繁体   English   中英

是否通过 C99 中未指定的联合进行类型双关,并已在 C11 中指定?

[英]Is type-punning through a union unspecified in C99, and has it become specified in C11?

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

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

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

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

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

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

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

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

联合类型双关的行为从 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 中所述(有时称为“类型双关”的过程)。 这可能是一个陷阱表示。

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

原始的 C99 规范未指定这一点。

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

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

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

这一直是“不确定的”。 正如其他人所指出的,通过技术更正在 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 中的-problematic 严格别名规则不允许(除了某些次要的例外)访问除其类型之外的对象。 由于“访问”是(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

(具体来说,两条注释行违反了严格别名规则)。

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

令人沮丧的是,负责制定标准的委员会似乎打算通过联合来实现类型双关,但似乎并没有因为标准的文本仍然不允许这样做而感到困扰。

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

结论:虽然人们普遍认为通过联合进行类型双关是合法的(大多数人认为只有在“通过联合类型”进行访问时才允许它,可以这么说),但标准的措辞禁止它在某些情况下进行琐碎的案件。

您引用的部分:

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

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

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

不,你很好。

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

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

union u
{
    float f;
    double d;
};

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

联合主要用于类型双关。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM