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

x7rlezfr  于 2023-06-28  发布在  其他
关注(0)|答案(4)|浏览(111)

关于堆栈溢出问题Getting the IEEE Single-precision bits for a float的许多答案建议使用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草案中至少有一个脚注似乎暗示情况不再如此(见www.example.com中的脚注956.5.2.3):
如果用于读取联合对象的内容的成员与最后用于存储对象中的值的成员不同,则值的对象表示的适当部分被重新解释为新类型中的对象表示,如6.2.6所述(有时称为“类型穿孔”的过程)。这可能是一个陷阱表示。
然而,C99草案中第6.2.6.1.7节的案文与C11草案中的案文相同。
这种行为实际上在C99中未指定吗?是否已在C11中指定?我意识到大多数编译器似乎都支持这一点,但如果知道它是在标准中指定的,还是只是一个非常常见的扩展,那就太好了。

eaf3rand

eaf3rand1#

带接头的打字机的性能从C89变为C99。C99中的行为与C11相同。
正如Wug在他的回答中所指出的,C99 / C11中允许类型双关。当联合成员的大小不同时,将读取可能是陷阱的未指定值。
在C99中克莱夫D.W.之后添加了脚注。羽毛Defect Report #257

**最后,从C90到C99的变化之一是,当最后一个存储是一个不同的存储时,删除了对访问联合的一个成员的任何限制。由于这一点经常被误解,因此很有必要在标准中予以明确。

[...]
为了解决有关“类型双关”的问题,在www.example.com中的单词“named member”附加新的脚注78 a 78 6.5.2.3#3: a如果用于访问联合对象的内容的成员与最后用于在对象中存储值的成员不相同,则值的对象表示的适当部分被重新解释为新类型中的对象表示,如6.2.6中所述(该过程有时称为“类型双关”)。这可能是一个陷阱表示。
克莱夫D.W.的措辞Feather被C委员会接受为Defect Report #283答案中的技术勘误。

ozxc1zmp

ozxc1zmp2#

最初的C99规范对此未作规定。
C99的一个技术勘误(我想是TR2)增加了脚注82来纠正这一疏忽:
如果用于访问联合对象内容的成员与最后用于在对象中存储值的成员不同,则值的对象表示的适当部分被重新解释为新类型中的对象表示,如6.2.6所述(有时称为“类型双关”的过程)。这可能是一个陷阱表示。
该脚注保留在C11标准中(它是C11中的脚注95)。

ymzxtsji

ymzxtsji3#

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

(特别是,这两个注解行将打破严格的别名规则)。
严格地说,脚注谈到一个单独的问题,即阅读一个不活跃的工会成员;然而,如上所述的严格别名规则与其他部分一起严重限制了其适用性,并且特别地意味着它一般不允许类型双关(但仅针对类型的特定组合)。
令人沮丧的是,负责制定该标准的委员会似乎打算通过联盟使类型双关成为可能,但似乎并没有因为标准的规范性文本仍然没有要求而感到困扰。
值得注意的是,(编译器供应商)的共识似乎是允许通过union进行类型双关,但“访问必须通过union类型”(例如上面示例中的第一个注解行,但不是第二个)。目前还不清楚这是否适用于读和写访问,并且没有得到标准文本的支持(忽略脚注)。

结论:虽然通过联合的类型双关是法律的的这一点在很大程度上被接受(可以说,大多数人认为只有在“通过联合类型”进行访问时才允许),但标准的规范性措辞在除了某些琐碎的情况之外的所有情况下都禁止它,并且在实践中,存在超出似乎允许类型双关的(非规范性)脚注所暗示的限制。
您引用的部分:
当值存储在联合类型对象的成员中时,对象表示中不对应于该成员但对应于其他成员的字节采用未指定的值。
但必须仔细阅读。“对象表示 * 中不对应于该成员 * 的字节”指的是超出成员大小的字节,这本身不是类型双关的问题(除了你不能假设写入联合成员将保持任何较大成员的“额外”部分不变)。

r1wp621o

r1wp621o4#

然而,这似乎违反了C99标准(至少是草案n1124),其中第6.2.6.1.7节规定了一些内容。这种行为实际上在C99中未指定吗?
不你没事
当值存储在联合类型对象的成员中时,对象表示中不对应于该成员但对应于其他成员的字节采用未指定的值。
这适用于不同大小的数据块。例如,如果你有:

union u
{
    float f;
    double d;
};

如果你给f赋值,它会改变d的低4个字节,但是高4个字节会处于不确定状态。
联合体主要用于类型双关。

相关问题