是否通过C99中未指定的联合进行类型惩罚,并且它是否在C11中指定?

sfs*_*man 58 c c99 unions type-punning c11

Stack Overflow问题的一些答案获取浮点数的IEEE单精度位建议使用union类型双关的结构(例如:将a的位float转换为a uint32_t):

union {
    float f;
    uint32_t u;
} un;
un.f = your_float;
uint32_t target = un.u;
Run Code Online (Sandbox Code Playgroud)

但是,uint32_t根据C99标准(至少草案n1124),联盟成员的值似乎未指定,其中第6.2.6.1.7节规定:

当值存储在union类型的对象的成员中时,对象表示的字节与该成员不对应但与其他成员对应的字节采用未指定的值.

C11 n1570草案至少有一个脚注似乎暗示不再是这种情况(见6.5.2.3中的脚注95):

如果用于读取union对象的内容的成员与上次用于在对象中存储值的成员不同,则将值的对象表示的适当部分重新解释为新类型中的对象表示形式在6.2.6中描述(一个过程有时被称为''punning'').这可能是陷阱表示.

但是,第C.6.6.1.7节中的案文与C11草案中的C99草案相同.

这种行为在C99下实际上是未指定的吗?它是否在C11中指定?我意识到大多数编译器似乎都支持这一点,但是知道它是在标准中指定还是只是一个非常常见的扩展会很好.

oua*_*uah 38

带有union的类型双关的行为从C89变为C99.C99中的行为与C11相同.

正如Wug在他的回答中指出的那样,在C99/C11中允许打字.当联合成员的大小不同时,将读取可能是陷阱的未指定值.

在克莱夫DW羽毛缺陷报告#257之后,在C99中添加了脚注:

最后,从C90到C99的一个变化是当最后一个商店到另一个商店时,取消了对访问一个工会成员的任何限制.理由是,行为将取决于价值的表示.由于这一点经常被误解,因此很可能值得在标准中明确说明.

[...]

要解决有关"打字"的问题,请在6.5.2.3#3中添加新的脚注78a到"命名成员"一词:78a如果用于访问联合对象内容的成员与上一个成员不同用于在对象中存储值,值的对象表示的适当部分被重新解释为新类型中的对象表示,如6.2.6中所述(有时称为"类型双关"的过程).这可能是陷阱表示.

在C #缺陷报告#283的答案中,Clive DW Feather的措辞被接受为技术勘误.

  • DR不清楚,脚注不是规范性的,只能解释其他地方的定义.此外,DR真的没有澄清任何事情.这个问题很混乱,因为工作组很困惑.(另外,Wug在"打字"的含义上是错误的.) (4认同)
  • @andrewrk我的结论是在C99和C11中允许打字.在写入成员后,您可以读取其他成员的陷阱表示,这一事实并未改变这一结论.这意味着在某些具有某些特定值的系统上,您可以调用未定义的行为.类似地,如果你使用带有某些特定操作数值的`*`二元运算符,你也容易出现未定义的行为(有符号整数溢出),这并不意味着运算符本身就是UB**或者不允许使用它. (2认同)

Ste*_*non 17

最初的C99规范没有具体说明.

C99(TR2,我认为)的技术勘误之一增加了脚注82来纠正这种疏忽:

如果用于访问union对象内容的成员与上次用于在对象中存储值的成员不同,则将值的对象表示的相应部分重新解释为新类型中的对象表示形式在6.2.6中描述(一个过程有时称为"类型双关语").这可能是陷阱表示.

该脚注保留在C11标准中(它是C11中的脚注95).

  • 不幸的是,无论是谁写的,都没有考虑到在编写一个联合成员和读取另一个成员时缺少定义的值是必要的,以证明当函数传递指向联合对象的不同成员的指针时编译器的行为是合理的。如果获取联合成员的地址并使用结果指针而不首先转换为“char”或使用 memcpy 是合法的,则标准中没有任何内容可以证明通过指针写入一个联合成员并读取另一个联合成员通常具有与写入不同的行为这一事实并直接读取工会成员。 (2认同)

dav*_*mac 5

这一直是“无聊的”。正如其他人指出的那样,已通过技术勘误表将脚注添加到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文档中仍然存在相同的文本。

但是,很明显,添加脚注的目的是为了进行类型处理。只是委员会似乎违反了不包含规范性文字的脚注中的规则。要接受脚注,您实际上必须无视说明脚注不是规范性的部分,或者尝试弄清楚如何以支持脚注结论的方式解释规范性文本(我已经尝试过,并且失败,去做)。

您引用的部分:

当值存储在联合类型的对象的成员中时,与该成员不对应但与其他成员对应的对象表示形式的字节采用未指定的值。

但是,必须仔细阅读。“ 与该成员不对应的对象表示形式的字节”指的是超出该成员大小的字节,这本身不是punning类型的问题(除非您不能假定写工会成员将导致未触及的任何较大成员的“额外”部分)。