在没有std :: launder的情况下将reinterpret_casting std :: aligned_storage *转换为T *是否违反严格混叠规则? [英] Does reinterpret_casting std::aligned_storage* to T* without std::launder violate strict-aliasing rules?

查看:114
本文介绍了在没有std :: launder的情况下将reinterpret_casting std :: aligned_storage *转换为T *是否违反严格混叠规则?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

以下示例来自cppreference.com的 std :: aligned_storage页面 :

#include <iostream>
#include <type_traits>
#include <string>

template<class T, std::size_t N>
class static_vector
{
    // properly aligned uninitialized storage for N T's
    typename std::aligned_storage<sizeof(T), alignof(T)>::type data[N];
    std::size_t m_size = 0;

public:
    // Create an object in aligned storage
    template<typename ...Args> void emplace_back(Args&&... args) 
    {
        if( m_size >= N ) // possible error handling
            throw std::bad_alloc{};
        new(data+m_size) T(std::forward<Args>(args)...);
        ++m_size;
    }

    // Access an object in aligned storage
    const T& operator[](std::size_t pos) const 
    {
        return *reinterpret_cast<const T*>(data+pos);
    }

    // Delete objects from aligned storage
    ~static_vector() 
    {
        for(std::size_t pos = 0; pos < m_size; ++pos) {
            reinterpret_cast<T*>(data+pos)->~T();
        }
    }
};

int main()
{
    static_vector<std::string, 10> v1;
    v1.emplace_back(5, '*');
    v1.emplace_back(10, '*');
    std::cout << v1[0] << '\n' << v1[1] << '\n';
}

在示例中,operator[]只是reinterpret_cast s std::aligned_storage*T*而没有std:launder,并直接执行间接.但是,根据此问题,这似乎是不确定的,甚至如果曾经创建过T类型的对象.

所以我的问题是:示例程序是否确实违反了严格的混叠规则?如果没有,我的理解力有什么问题?

解决方案

我问了 P0137 之前,请先参阅到[basic.compound]第3段:

如果类型T的对象位于地址A,则无论其值如何获得,都将以其值为地址A的cv T *类型的指针指向该对象.

和[expr.static.cast]第13段:

如果原始指针值表示内存中一个字节的地址A,并且A满足T的对齐要求,则所得指针值表示与原始指针值相同的地址,即A.

表达式reinterpret_cast<const T*>(data+pos)表示先前创建的类型为T的对象的地址,因此指向该对象.通过该指针进行的间接访问确实获得了定义明确的对象.

但是,在P0137之后,将更改指针值的定义,并删除第一个用引号引起来的字.现在请参考[basic.compound]第3段:

指针类型的每个值都是以下之一:

  • 指向对象或函数的指针(据说该指针指向该对象或函数),或者

  • ...

和[expr.static.cast]第13段:

如果原始指针值表示内存中一个字节的地址A,而A不满足T的对齐要求,则未指定结果指针值.否则,如果原始指针值指向对象a,并且存在类型T(忽略cv限定)的对象b,该对象可与a进行指针互换,则结果是指向b的指针. 否则,转换后指针值将保持不变.

表达式reinterpret_cast<const T*>(data+pos)仍指向类型为std::aligned_storage<...>::type的对象,尽管左值的类型为const T,但间接获得指向该对象的左值.在示例中,对表达式v1[0]的求值尝试通过lvalue访问std::aligned_storage<...>::type对象的值,根据[basic.lval]第11段(即严格混叠规则),这是未定义的行为:

如果程序尝试通过以下类型之一以外的glvalue访问对象的存储值,则行为未定义:

  • 对象的动态类型

  • 对象动态类型的cv限定版本,

  • 与对象的动态类型相似的类型(在[conv.qual]中定义)

  • 一种类型,它是与对象的动态类型相对应的有符号或无符号类型

  • 一种类型,它是与对象的动态类型的cv限定版本相对应的有符号或无符号类型

  • 一种聚集或联合类型,在其元素或非静态数据成员(包括递归地包括子聚集或所包含的联合的元素或非静态数据成员)中包括上述类型之一, p>

  • 一种类型,它是对象动态类型的(可能是cv限定的)基类类型,

  • char,unsigned char或std :: byte类型.

The following example comes from std::aligned_storage page of cppreference.com:

#include <iostream>
#include <type_traits>
#include <string>

template<class T, std::size_t N>
class static_vector
{
    // properly aligned uninitialized storage for N T's
    typename std::aligned_storage<sizeof(T), alignof(T)>::type data[N];
    std::size_t m_size = 0;

public:
    // Create an object in aligned storage
    template<typename ...Args> void emplace_back(Args&&... args) 
    {
        if( m_size >= N ) // possible error handling
            throw std::bad_alloc{};
        new(data+m_size) T(std::forward<Args>(args)...);
        ++m_size;
    }

    // Access an object in aligned storage
    const T& operator[](std::size_t pos) const 
    {
        return *reinterpret_cast<const T*>(data+pos);
    }

    // Delete objects from aligned storage
    ~static_vector() 
    {
        for(std::size_t pos = 0; pos < m_size; ++pos) {
            reinterpret_cast<T*>(data+pos)->~T();
        }
    }
};

int main()
{
    static_vector<std::string, 10> v1;
    v1.emplace_back(5, '*');
    v1.emplace_back(10, '*');
    std::cout << v1[0] << '\n' << v1[1] << '\n';
}

In the example, the operator[] just reinterpret_casts std::aligned_storage* to T* without std:launder, and performs an indirection directly. However, according to this question, this seems to be undefined, even if an object of type T has been ever created.

So my question is: does the example program really violate strict-aliasing rules? If it does not, what's wrong with my comprehension?

解决方案

I asked a related question in the ISO C++ Standard - Discussion forum. I learned the answer from those discussions, and write it here to hope to help someone else who is confused about this question. I will keep updating this answer according to those discussions.

Before P0137, refer to [basic.compound] paragraph 3:

If an object of type T is located at an address A, a pointer of type cv T* whose value is the address A is said to point to that object, regardless of how the value was obtained.

and [expr.static.cast] paragraph 13:

If the original pointer value represents the address A of a byte in memory and A satisfies the alignment requirement of T, then the resulting pointer value represents the same address as the original pointer value, that is, A.

The expression reinterpret_cast<const T*>(data+pos) represents the address of the previously created object of type T, thus points to that object. Indirection through this pointer indeed get that object, which is well-defined.

However after P0137, the definition for a pointer value is changed and the first block-quoted words is deleted. Now refer to [basic.compound] paragraph 3:

Every value of pointer type is one of the following:

  • a pointer to an object or function (the pointer is said to point to the object or function), or

  • ...

and [expr.static.cast] paragraph 13:

If the original pointer value represents the address A of a byte in memory and A does not satisfy the alignment requirement of T, then the resulting pointer value is unspecified. Otherwise, if the original pointer value points to an object a, and there is an object b of type T (ignoring cv-qualification) that is pointer-interconvertible with a, the result is a pointer to b. Otherwise, the pointer value is unchanged by the conversion.

The expression reinterpret_cast<const T*>(data+pos) still points to the object of type std::aligned_storage<...>::type, and indirection get a lvalue referring to that object, though the type of the lvalue is const T. Evaluation of the expression v1[0] in the example tries to access the value of the std::aligned_storage<...>::type object through the lvalue, which is undefined behavior according to [basic.lval] paragraph 11 (i.e. the strict-aliasing rules):

If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined:

  • the dynamic type of the object,

  • a cv-qualified version of the dynamic type of the object,

  • a type similar (as defined in [conv.qual]) to the dynamic type of the object,

  • a type that is the signed or unsigned type corresponding to the dynamic type of the object,

  • a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,

  • an aggregate or union type that includes one of the aforementioned types among its elements or non-static data members (including, recursively, an element or non-static data member of a subaggregate or contained union),

  • a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,

  • a char, unsigned char, or std​::​byte type.

这篇关于在没有std :: launder的情况下将reinterpret_casting std :: aligned_storage *转换为T *是否违反严格混叠规则?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆