64

Stack Overflow 问题Getting the IEEE Single-precision bits for a float的一些答案建议使用union结构来进行类型双关(例如:将 a 的位float转换为 a uint32_t):

union {
    float f;
    uint32_t u;
} un;
un.f = your_float;
uint32_t target = un.u;

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

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

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

如果用于读取联合对象内容的成员与上次用于在对象中存储值的成员不同,则将值的对象表示的适当部分重新解释为新类型中的对象表示在 6.2.6 中描述(有时称为“类型双关语”的过程)。这可能是一个陷阱表示。

但是,C99 草案中的第 6.2.6.1.7 节的文本与 C11 草案中的文本相同。

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

4

4 回答 4

43

使用 union 的类型双关语的行为从 C89 更改为 C99。C99 中的行为与 C11 相同。

正如Wug在他的回答中指出的那样,C99 / C11 中允许使用类型双关语。当联合成员的大小不同时,将读取可能是陷阱的未指定值。

在 Clive DW Feather Defect Report #257之后的 C99 中添加了脚注:

最后,从 C90 到 C99 的变化之一是取消了当最后一个商店指向不同的商店时访问联合的一个成员的任何限制。基本原理是行为将取决于值的表示。由于这一点经常被误解,因此可能值得在标准中明确说明。

[...]

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

C 委员会缺陷报告 #283的回答中接受了 Clive DW Feather 的措辞作为技术勘误。

于 2012-07-24T23:09:21.527 回答
19

最初的 C99 规范没有说明这一点。

C99 的一项技术勘误(我认为是 TR2)添加了脚注 82 以纠正这一疏忽:

如果用于访问联合对象内容的成员与最后用于在对象中存储值的成员不同,则该值的对象表示的适当部分将被重新解释为新类型中的对象表示在 6.2.6 中描述(有时称为“类型双关语”的过程)。这可能是一个陷阱表示。

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

于 2012-07-24T22:46:16.420 回答
9

这一直是“不确定的”。正如其他人所指出的,通过技术更正向 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

(具体来说,这两条注释行打破了严格的混叠规则)。

严格来说,脚注涉及一个单独的问题,即阅读不活跃的工会成员。然而,严格别名规则与上面提到的其他部分一起严重限制了它的适用性,特别是意味着它通常不允许类型双关语(但仅适用于特定的类型组合)。

令人沮丧的是,负责制定标准的委员会似乎打算通过一个联合来实现类型双关语,但似乎并不担心标准文本仍然不允许它。

还值得注意的是,(编译器供应商)的共识理解似乎是允许通过联合进行类型双关,但“必须通过联合类型访问”(例如上面示例中的第一条注释行,但不是第二条)。有点不清楚这是否应该适用于读取和写入访问,并且标准文本绝不支持(忽略脚注)。

总之:虽然通过联合的类型双关语在很大程度上被接受是合法的(大多数人认为只有当访问是“通过联合类型”完成时才允许,可以这么说),标准的措辞几乎禁止它琐碎的案例。

您引用的部分:

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

......不过,必须仔细阅读。“与该成员不对应的对象表示的字节”是指超出成员大小的字节,这本身不是类型双关语的问题(除非您不能假设写入联合成员会离开任何较大成员的“额外”部分未触及)。

于 2016-04-18T22:48:34.857 回答
0

但是,这似乎违反了 C99 标准(至少是 n1124 草案),其中第 6.2.6.1.7 节规定了一些内容。这种行为在 C99 下实际上是未指定的吗?

不,你很好。

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

这适用于不同大小的数据块。即,如果您有:

union u
{
    float f;
    double d;
};

如果你给 f 赋值,它会改变 d 的低 4 字节,但高 4 字节将处于不确定状态。

联合主要用于类型双关语。

于 2012-07-24T22:02:25.347 回答