这是 C++ 中未定义的行为吗?

o_o*_*tle 6 c++ strict-aliasing language-lawyer c++20

此示例是从cppreference复制的。

struct Y { int z; };
alignas(Y) std::byte s[sizeof(Y)];
Y* q = new(&s) Y{2};
const int f = reinterpret_cast<Y*>(&s)->z; // Class member access is undefined
                                           // behavior: reinterpret_cast<Y*>(&s)
                                           // has value "pointer to s" and does
                                           // not point to a Y object
const int g = q->z; // OK
const int h = std::launder(reinterpret_cast<Y*>(&s))->z; // OK
Run Code Online (Sandbox Code Playgroud)

我想知道像s[0] = std::byte{0}上面的语句之后添加操作是否是未定义的行为?看起来它并没有违反严格的别名规则,因为std::byte根据cppreference可以是任何类型的“AliasedType” ,这意味着将任何对象视为字节数组是合法的。

请注意,我添加了 c++20 标签,因为它们可能只有在 C++20 之后才被明确定义。

Lan*_*ern 2

感谢@LanguageLawyer 的更正,我更新了我的答案。


我想知道在上面的语句之后添加像 s[0] = std::byte{0} 这样的操作是否是未定义的行为?

我相信这是一种未定义的行为,但其原因与严格的别名规则无关。D严格的别名规则规定,如果程序通过类型的左值访问类型的对象T,但DT不“相似”并且T不是那些特殊字符类型之一,则程序具有未定义的行为。在您的情况下,您std::byte通过 type 的泛左值访问对象std::byte,这在严格的别名规则下完全没问题。

这里真正的问题是s还为另一个对象提供存储Y。一旦s为对象提供了存储Y(在 new 表达式之后),数组元素的生命周期就会终止,因为它们的存储被重用:

[基本生活]/1.2:

T 类型的对象 o 的生命周期在以下情况结束:

  • 如果 T 是非类类型,则该对象被销毁,或者
  • 如果 T 是类类型,则析构函数调用开始,或者
  • 对象占用的存储被释放,或者被未嵌套在 o ([intro.object]) 中的对象重用。

因此,如果您在 new 表达式之后访问s[0],您将访问一个过期对象,这是一种未定义的行为。