这种类型的双关语是否定义良好
Is this type punning well-defined?
阅读这个答案中关于严格混叠规则的引用,我看到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
AliasedType
和DynamicType
都是指向同一类型T
的指针(可能是多级的,可能是每个级别的CV合格)AliasedType
是DynamicType
的(可能符合 CV 条件的)有符号或无符号变体AliasedType
是一种聚合类型或联合类型,它将上述类型之一保存为元素或非静态成员(包括递归的子聚合元素和所包含联合的非静态数据成员):这使得在给定指向其非静态成员或元素的指针的情况下,可以安全地获取指向结构或联合的可用指针。AliasedType
是(可能符合 CV 条件的)DynamicType
基本类AliasedType
是char
或unsigned char
:这允许检查任何对象的对象表示为unsigned char
数组
由于对于float
DynamicType
和AliasedType
PunnerToUInt32
的组合,这些都不正确,因此指针可能不用于访问您正在执行的操作的对象。使行为未定义。
有关详细信息,请参阅:为什么reinterpret_cast不强制copy_n相同大小类型之间的转换?
编辑:
分解第 4 个子弹 int 咬大小块得到:
- "
AliasedType
">
这里被认为是PunnerToUInt32
-
"是聚合类型或联合类型">
PunnerToUInt32
符合条件,因为它满足聚合类型的资格:- 阵列类型
- 类类型(通常为
struct
或union
),具有- 没有私有或受保护的非静态数据成员
- 没有用户提供的构造函数,包括从公共基继承的构造函数(允许显式默认或删除构造函数)
- 没有虚拟、私有或受保护的基类
- 无虚拟成员函数
-
"将上述类型之一作为元素或非静态成员(包括递归的子聚合元素和所包含联合的非静态数据成员)">
再次PunnerToUInt32
有资格,因为它float fl
成员 - "这使得获得指向结构或联合的可用指针变得安全"这是最终正确的部分,
因为AliassedType
是一个PunnerToUInt32
- "给定指向其非静态成员或元素的指针">
这是违规行为,因为x
的DynamicType
不是PunnerToUInt32
的成员
由于违反第 5 部分,在此指针上运行是未定义的行为。
如果你关心一些推荐的阅读,你可以看看 空基优化 如果没有,我会在这里给你主要相关性:
StandardLayoutType 需要空基优化,以保持指向标准布局对象的指针(使用
reinterpret_cast
转换)指向其初始成员的要求
因此,你可以通过这样做来利用reinterpret_cast
的第 4个项目符号:
PunnerToUInt32 x = {13, 42.0F};
auto y = reinterpret_cast<PunnerToUInt32*>(&x.ui32);
现场示例
如果p_x_as_uint32
以某种方式指向x
1,那么*p_x_as_uint32=5
将通过类型uint32_t
的glvalue访问类型float
的对象,这将导致未定义的行为。
有争议的"访问"是分配,所有重要的是所使用的glvalue的类型(uint32_t
)和访问对象的实际类型(float
)。用于获得指针的一系列折磨的演员是无关紧要的。
值得记住的是,存在严格的别名规则以启用基于类型的别名分析。无论采取的路线多么折磨人,如果你能合法地"创造一种情况,即int*
和float*
可以同时存在,并且两者都可以用来加载或存储相同的内存,你就摧毁了TBAA"。如果你认为标准的措辞以某种方式允许你这样做,你可能错了,但如果你是对的,那么你发现的只是标准措辞的缺陷。
1类成员访问是未定义的行为(通过省略),因为没有实际的PunnerToUInt32
对象。但是,类成员访问不是严格别名规则意义上的"访问";后者的意思是"读取或修改对象的值"。
- 此递归模板类型定义是否有效C++?
- 虚拟成员函数的定义是否强制在同一转换单元中动态初始化静态数据成员?
- 越界成员函数定义是否需要一个完全限定的类名,直到全局范围
- C++标准是否定义了结构中成员函数的函数内定义是否必须具有静态链接?
- 带有"struct structname<..>"的模板定义是否正确?
- NTRUEncrypt:使用开源标准算法中的描述无法正确找到两个多项式的GCD,无法定义是否存在多边形的逆
- 定义重载C++函数模板的原型时,使用其名称引用以前的定义是否合法?
- 命名空间的定义是否可以跨越多个翻译单元
- 头文件中的类声明和定义是否在每个包含上编译
- 这个无括号的C预处理器定义是否安全
- char的实现定义是否会影响std::string
- 对内联函数有不同的定义是否是一种未定义的行为
- 同一字符串的多个#定义是否使用相同的常量字符串
- 根据编译时常量,使用相同的标识符 #define 或类型定义是否被认为是可接受的做法?
- 显式模板实例化定义是否也抑制隐式实例化
- 构造函数定义是否可以以 "class" 关键字为前缀?
- 类中的函数定义是否占用类大小的一部分?
- 名称空间作用域构造函数定义是否需要类限定的标识符
- i=i++;未定义.是否i=foo(i++)也未定义
- "volatile"的定义是否如此不稳定,还是 GCC 存在一些标准合规性问题?