这种类型的双关语是否定义良好

Is this type punning well-defined?

本文关键字:定义 是否 双关语 种类 类型      更新时间:2023-10-16

阅读这个答案中关于严格混叠规则的引用,我看到C++11以下内容:

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

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

所以我认为这意味着以下代码不会违反严格的别名规则:

#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的规则规定:

当对动态类型为DynamicType的对象的指针或引用reinterpret_cast(或 C 样式强制转换)到指向不同类型的对象的指针或引用AliasedType时,强制转换始终成功,但生成的指针或引用只能在满足以下条件之一时用于访问对象:

  • AliasedType是(可能符合简历条件的)DynamicType
  • AliasedTypeDynamicType都是指向同一类型T的指针(可能是多级的,可能是每个级别的CV合格)
  • AliasedTypeDynamicType的(可能符合 CV 条件的)有符号或无符号变体
  • AliasedType是一种聚合类型或联合类型,它将上述类型之一保存为元素或非静态成员(包括递归的子聚合元素和所包含联合的非静态数据成员):这使得在给定指向其非静态成员或元素的指针的情况下,可以安全地获取指向结构或联合的可用指针。
  • AliasedType是(可能符合 CV 条件的)DynamicType基本类
  • AliasedTypecharunsigned char:这允许检查任何对象的对象表示为unsigned char数组

由于对于floatDynamicTypeAliasedTypePunnerToUInt32的组合,这些都不正确,因此指针可能不用于访问您正在执行的操作的对象。使行为未定义。

有关详细信息,请参阅:为什么reinterpret_cast不强制copy_n相同大小类型之间的转换?

编辑:

分解第 4 个子弹 int 咬大小块得到:

  1. "AliasedType">
    这里被认为是PunnerToUInt32
  2. "是聚合类型或联合类型">
    PunnerToUInt32符合条件,因为它满足聚合类型的资格:

    • 阵列类型
    • 类类型(通常为structunion),具有
      • 没有私有或受保护的非静态数据成员
      • 没有用户提供的构造函数,包括从公共基继承的构造函数(允许显式默认或删除构造函数)
      • 没有虚拟、私有或受保护的基类
      • 无虚拟成员函数
  3. "将上述类型之一作为元素或非静态成员(包括递归的子聚合元素和所包含联合的非静态数据成员)">
    再次PunnerToUInt32有资格,因为它float fl成员

  4. "这使得获得指向结构或联合的可用指针变得安全"这是最终正确的部分,
    因为AliassedType是一个PunnerToUInt32
  5. "给定指向其非静态成员或元素的指针">
    这是违规行为,因为xDynamicType不是PunnerToUInt32
  6. 的成员

由于违反第 5 部分,在此指针上运行是未定义的行为。

如果你关心一些推荐的阅读,你可以看看 空基优化 如果没有,我会在这里给你主要相关性:

StandardLayoutType 需要空基优化,以保持指向标准布局对象的指针(使用reinterpret_cast转换)指向其初始成员的要求

因此,你可以通过这样做来利用reinterpret_cast的第 4项目符号:

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

现场示例

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

有争议的"访问"是分配,所有重要的是所使用的glvalue的类型(uint32_t)和访问对象的实际类型(float)。用于获得指针的一系列折磨的演员是无关紧要的。

值得记住的是,存在严格的别名规则以启用基于类型的别名分析。无论采取的路线多么折磨人,如果你能合法地"创造一种情况,即int*float*可以同时存在,并且两者都可以用来加载或存储相同的内存,你就摧毁了TBAA"。如果你认为标准的措辞以某种方式允许你这样做,你可能错了,但如果你是对的,那么你发现的只是标准措辞的缺陷。


1类成员访问是未定义的行为(通过省略),因为没有实际的PunnerToUInt32对象。但是,类成员访问不是严格别名规则意义上的"访问";后者的意思是"读取或修改对象的值"。