这种类型的双关语定义明确吗?

Is this type punning well-defined?

阅读 this answer 中关于严格别名规则的引述,我看到以下针对 C++11 的内容:

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:

  • ...

  • 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),

  • ...

所以我认为下面的代码没有违反严格的别名规则:

#include <iostream>
#include <cstdint>
#include <climits>
#include <limits>

struct PunnerToUInt32
{
    std::uint32_t ui32;
    float fl;
};

int main()
{
    static_assert(std::numeric_limits<float>::is_iec559 &&
                  sizeof(float)==4 && CHAR_BIT==8,"Oops");
    float x;
    std::uint32_t* p_x_as_uint32=&reinterpret_cast<PunnerToUInt32*>(&x)->ui32;
    *p_x_as_uint32=5;
    std::cout << x << "\n";
}

很好,满足严格的别名规则。由于任何其他原因,这是否仍然表现出未定义的行为?

你不能这样做:&reinterpret_cast<PunnerToUInt32*>(&x)

reinterpret_cast状态的规则:

When a pointer or reference to object whose dynamic type is DynamicType is reinterpret_cast (or C-style cast) to a pointer or reference to object of a different type AliasedType, the cast always succeeds, but the resulting pointer or reference may only be used to access the object if one of the following is true:

  • AliasedType is (possibly cv-qualified) DynamicType
  • AliasedType and DynamicType are both (possibly multi-level, possibly cv-qualified at each level) pointers to the same type T
  • AliasedType is the (possibly cv-qualified) signed or unsigned variant of DynamicType
  • AliasedType is an aggregate type or a union type which holds one of the aforementioned types as an element or non-static member (including, recursively, elements of subaggregates and non-static data members of the contained unions): this makes it safe to obtain a usable pointer to a struct or union given a pointer to its non-static member or element.
  • AliasedType is a (possibly cv-qualified) base class of DynamicType
  • AliasedType is char or unsigned char: this permits examination of the object representation of any object as an array of unsigned char

因为 none 对于 DynamicTypefloatAliasedTypePunnerToUInt32 的组合是正确的,指针不能用于访问你正在做的对象。使行为未定义。

有关详细信息,请参阅:

编辑:

分解第 4 个 bullet int bite size 块产量:

  1. "AliasedType"
    这里取为PunnerToUInt32
  2. "is an aggregate type or a union type"
    PunnerToUInt32 符合条件,因为它符合 aggregate type:

    的条件
    • array type
    • class type (typically, struct or union), that has
      • no private or protected non-static data members
      • no user-provided constructors, including those inherited from public bases (explicitly defaulted or deleted constructors are allowed)
      • no virtual, private, or protected base classes
      • no virtual member functions
  3. "which holds one of the aforementioned types as an element or non-static member (including, recursively, elements of subaggregates and non-static data members of the contained unions)"
    再次 PunnerToUInt32 符合条件,因为它是 float fl 成员

  4. "this makes it safe to obtain a usable pointer to a struct or union"
    这是最后正确的部分,因为 AliassedTypePunnerToUInt32
  5. "given a pointer to its non-static member or element"
    这是一个违规行为,因为 DynamicTypex 不是 PunnerToUInt32
  6. 的成员

因为违反第​​ 5 部分操作这个指针是未定义的行为。

如果你喜欢一些推荐的阅读,你可以查看 Empty Base Optimization 如果没有,我会在这里给你主要的相关性:

Empty base optimization is required for StandardLayoutTypes in order to maintain the requirement that the pointer to a standard-layout object, converted using reinterpret_cast, points to its initial member

因此你可以通过这样做来利用 reinterpret_cast 的第 4 颗子弹:

PunnerToUInt32 x = {13, 42.0F};
auto y = reinterpret_cast<PunnerToUInt32*>(&x.ui32);

Live Example

如果 p_x_as_uint32 以某种方式指向 x1,那么 *p_x_as_uint32=5 将通过以下方式访问 float 类型的对象uint32_t 类型的左值,这将导致未定义的行为。

问题中的 "access" 是赋值,重要的是使用的左值类型 (uint32_t) 和访问对象的实际类型 (float) .用于获取指针的一系列强制转换是无关紧要的。

值得记住的是,存在严格的别名规则以启用基于类型的别名分析。不管走的路有多折磨,if you can legally "create a situation where an int* and a float* can simultaneously exist and both can be used to load or store the same memory, you destroy TBAA"。如果您认为标准的措辞以某种方式允许您这样做,那么您可能错了,但如果您是对的,那么您所发现的只是标准措辞中的一个缺陷。


1 class 成员访问是未定义的行为(由于遗漏),因为没有实际的 PunnerToUInt32 对象。但是,class 成员访问不是严格别名规则意义上的 "access";后者表示 "to read or modify the value of an object".