令人困惑的是,C 明确允许通过联合进行类型双关,而 C++ (c++11) 没有这样的权限。
c11
6.5.2.3 结构和联合成员
95) 如果用于读取联合对象内容的成员与上次用于读取的成员不同
在对象中存储一个值,该值的对象表示的适当部分被重新解释
作为 6.2.6 中描述的新类型中的对象表示(有时称为“类型
双关语'')。这可能是一个陷阱表示。
C++的情况:
c++11
9.5 联合 [class.union]
在一个联合中,任何时候最多可以有一个非静态数据成员处于活动状态,即at的值
大多数非静态数据成员可以随时存储在联合中。
后来的 C++ 语言允许使用包含structs 和公共初始序列的联合;但是,这不允许类型双关。
要确定在 C++ 中是否允许联合类型双关 ,我们必须进一步搜索。回想一下 c99 是 C++11 的规范参考(C99 与 C11 有相似的语言,允许联合类型双关语):
3.9 类型 [basic.types]
4 - T 类型对象的对象表示是 N 个 unsigned char 对象的序列
T 类型的对象,其中 N 等于 sizeof(T)。对象的值表示是一组位
保存类型 T 的值。对于普通可复制类型,值表示是对象中的一组位
确定一个值的表示,它是实现定义的一组离散元素
价值观。 42
42) 目的是 C++ 的内存模型与 ISO/IEC 9899 编程语言 C 的内存模型兼容。
当我们阅读时会变得特别有趣
3.8 对象生命周期 [basic.life]
类型 T 的对象的生命周期开始于:
— 获得了类型 T 的正确对齐和大小的存储,并且
— 如果对象有非平凡的初始化,它的初始化就完成了。
因此,对于包含在联合中的原始类型(ipso facto 具有简单的初始化),对象的生命周期至少包含联合本身的生命周期。这允许我们调用
3.9.2 复合类型 [basic.compound]
如果 T 类型的对象位于地址 A,则为 cv T* 类型的指针,其值为
无论值是如何获得的,地址 A 都被称为指向该对象。
假设我们感兴趣的操作是类型双关语,即获取非活动联合成员的值,并且根据上述我们对该成员引用的对象具有有效引用,则该操作是左值到右值的转换:
4.1 左值到右值的转换[conv.lval]
非函数、非数组类型T 的泛左值可以转换为纯右值。
如果T 是不完整的类型,则需要进行此转换的程序格式错误。 如果泛左值所引用的对象不是T 类型的对象,也不是从T 派生的类型的对象,或者如果该对象未初始化,则需要此转换的程序具有未定义的行为.
接下来的问题是,作为非活动联合成员的对象是否通过存储初始化到活动联合成员。据我所知,情况并非如此,尽管如果:
- 联合复制到
char 数组存储并返回 (3.9:2),或者
- 一个联合被按字节复制到另一个相同类型的联合 (3.9:3),或者
- 符合 ISO/IEC 9899(就其定义而言)的程序元素跨语言边界访问联合(3.9:4 注 42),然后
非活动成员对联合的访问已定义并定义为遵循对象和值表示,没有上述插入之一的访问是未定义的行为。这对允许在此类程序上执行的优化有影响,因为实现当然可能假设未发生未定义的行为。
也就是说,虽然我们可以合法地为非活动的联合成员形成一个左值(这就是为什么在没有构造的情况下分配给非活动成员是可以的),但它被认为是未初始化的。