这一直以来都有点“靠不住”。正如其他人所指出的,通过技术勘误,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中关于联合体定义为“重叠对象集合”的一些假设:
联合类型描述了一个重叠的非空成员对象集合,每个对象都可以有一个可选的名称和可能不同的类型。
很遗憾,对于“重叠”一词并没有详细的解释。一个对象被定义为在执行环境中的“数据存储区域,其内容可以表示值”(根据上述“重叠对象”的定义,同一存储区域可以被两个或更多不同的对象所标识,也就是说,对象具有与其存储区域分离的身份)。合理的假设似乎是联合体成员(特定联合体实例的成员)使用相同的存储区域。
即使我们忽略6.7.2.1/6.5.2.3,并允许根据脚注的建议,读取任何联合体成员都返回与相应存储区域的内容相对应的值——这将允许类型转换——然而,6.5中令人头疼的严格别名规则(strict-aliasing rule)禁止(除了某些小的例外情况)通过非本类型来访问对象。由于“访问”是指(3.1)“读取或修改对象的值的〈执行时动作〉”,并且由于修改一组重叠对象中的一个必然会修改其他对象,因此通过写入联合体成员(无论是否通过另一个成员进行读取)可能会违反严格别名规则。
例如,根据标准的措辞以及每个成员作为一个独立对象存在且重叠存储的概念,以下情况似乎是非法的:
union {
int a;
float b;
} u;
u.b = 0.5; // store a float value in the union object subobject
u.a = 0; // (#1) modifies a float object by an lvalue of type int
int *pa = &u.a;
*pa = 1; // (#2) also modifies a float object, without union lvalue involved
具体来说,标记为#1和#2的行将违反严格别名规则。在这两种情况下,如果将值存储到成员中会清除先前活动成员的值,那么可能可以避免这种情况,正如6.7.2.1所建议的那样,尽管先前指出这基本上禁止了通过联合进行类型转换。
严格来说,脚注涉及一个单独的问题,即读取非活动联合成员;然而,严格别名规则与上述其他部分结合使用严重限制了其适用性,特别是意味着它不允许一般的类型转换(只允许特定类型的组合)。
令人沮丧的是,负责制定标准的委员会似乎打算通过联合通常实现类型转换,但似乎并不担心标准的规范文本仍然没有对此提出要求。
还值得注意的是,共识理解(由编译器供应商)似乎是允许通过联合进行类型转换,但“访问必须通过联合类型”(例如上面示例中的第一行注释,但不包括第二行)。目前还不太清楚这是否适用于读取和写入访问,并且在标准文本中没有任何支持(忽略脚注)。
总结一下:虽然普遍认为通过联合体进行类型游戏是合法的(大多数人认为只有在“通过联合类型”访问时才允许,可以这么说),但标准的规范措辞禁止除某些琐碎情况外的所有类型游戏,并且实际上存在着超出(非规范的)脚注所暗示的类型游戏的限制。
你引用的那一节需要仔细阅读:“当一个值存储在联合类型对象的成员中时,与该成员不对应但与其他成员对应的对象表示的字节将具有未指定的值。”
然而,必须仔细阅读这句话。“与该成员不对应的对象表示的字节”是指超出成员大小的字节,这本身并不是类型游戏的问题(除了你不能假设写入联合成员会保持任何更大成员的“额外”部分不变)。