这一直是“不确定的”。正如其他人所指出的,通过技术更正向 C99 添加了脚注。内容如下:
如果用于访问联合对象内容的成员与上次用于在对象中存储值的成员不同,则该值的对象表示的适当部分被重新解释为新类型中的对象表示在 6.2.6 中描述(有时称为“类型双关语”的过程)。这可能是一个陷阱表示。
但是,前言中的脚注被指定为非规范性的:
附录 D 和 F 构成本标准的规范性部分;附件 A、B、C、E、G、H、I、J、参考书目和索引仅供参考。根据 ISO/IEC 指令的第 3 部分,本前言、引言、注释、脚注和示例也仅供参考。
也就是说,脚注不能禁止行为;他们只应澄清现有案文。这是一个不受欢迎的意见,但上面引用的脚注在这方面实际上是失败的——规范文本中没有禁止这种行为。确实有矛盾的部分,比如6.7.2.1:
... 最多一个成员的值可以随时存储在联合对象中
结合 6.5.2.3(关于使用“.”操作符访问联合成员):
该值是命名成员的值
即如果只能存储一个成员的值,则另一个成员的值不存在。这强烈暗示不应该通过联合进行类型双关语;成员访问产生一个不存在的值。C11 文档中仍然存在相同的文本。
但是,很明显,添加脚注的目的是为了允许使用双关语;只是委员会似乎违反了不包含规范性文本的脚注规则。要接受脚注,你真的必须忽略脚注不是规范性的部分,或者试图弄清楚如何以支持脚注结论的方式解释规范性文本(我已经尝试过,并且失败,做)。
我们可以做的最好的批准脚注是对联合的定义做出一些假设,即从 6.2.5 开始的一组“重叠对象”:
联合类型描述了一组重叠的非空成员对象,每个成员对象都有一个可选的指定名称和可能的不同类型
不幸的是,没有详细说明“重叠”的含义。对象被定义为(3.14)“执行环境中的数据存储区域,其内容可以表示值”(相同的存储区域可以由两个或多个不同的对象标识,这由“重叠对象”暗示” 上面的定义,即对象具有与其存储区域分开的标识)。合理的假设似乎是(特定联合实例的)联合成员使用相同的存储区域。
即使我们忽略 6.7.2.1/6.5.2.3 并允许,如脚注所示,读取任何联合成员返回将由相应存储区域的内容表示的值——因此将允许类型双关——永远- 6.5 中的有问题的严格别名规则不允许(除了某些次要例外)访问不是按其类型的对象。由于“访问”是(3.1)“<执行时动作>读取或修改对象的值”,并且由于修改一组重叠对象中的一个必然会修改其他对象,因此严格混叠规则可以写给工会成员可能会违反(不管它是否被另一个人阅读)。
例如,根据标准的措辞,以下内容是非法的:
union {
int a;
float b;
} u;
u.a = 0; // modifies a float object by an lvalue of type int
int *pa = &u.a;
*pa = 1; // also modifies a float object, without union lvalue involved
(具体来说,这两条注释行打破了严格的混叠规则)。
严格来说,脚注涉及一个单独的问题,即阅读不活跃的工会成员。然而,严格别名规则与上面提到的其他部分一起严重限制了它的适用性,特别是意味着它通常不允许类型双关语(但仅适用于特定的类型组合)。
令人沮丧的是,负责制定标准的委员会似乎打算通过一个联合来实现类型双关语,但似乎并不担心标准文本仍然不允许它。
还值得注意的是,(编译器供应商)的共识理解似乎是允许通过联合进行类型双关,但“必须通过联合类型访问”(例如上面示例中的第一条注释行,但不是第二条)。有点不清楚这是否应该适用于读取和写入访问,并且标准文本绝不支持(忽略脚注)。
总之:虽然通过联合的类型双关语在很大程度上被接受是合法的(大多数人认为只有当访问是“通过联合类型”完成时才允许,可以这么说),标准的措辞几乎禁止它琐碎的案例。
您引用的部分:
当一个值存储在联合类型对象的成员中时,对象表示中不对应于该成员但对应于其他成员的字节采用未指定的值。
......不过,必须仔细阅读。“与该成员不对应的对象表示的字节”是指超出成员大小的字节,这本身不是类型双关语的问题(除非您不能假设写入联合成员会离开任何较大成员的“额外”部分未触及)。