联盟不重新诠释价值观?

Union not reinterpreting values?

考虑这个程序:

#include <stdio.h>

union myUnion
{
    int x;
    long double y;
};

int main()
{
    union myUnion a;
    a.x = 5;
    a.y = 3.2;
    printf("%d\n%.2Lf", a.x, a.y);
    return 0;
}

输出:

-858993459
3.20

这很好,因为 int 成员使用 long double 成员的某些位进行解释。然而,反过来并不适用:

#include <stdio.h>

union myUnion
{
    int x;
    long double y;
};

int main()
{
    union myUnion a;
    a.y = 3.2;
    a.x = 5;
    printf("%d\n%.2Lf", a.x, a.y);
    return 0;
}

输出:

5
3.20

问题是为什么 long double 没有被重新解释为一些垃圾值(因为它的 4 个字节应该代表整数)?这不是巧合,程序为 a.x 的所有值输出 3.20,而不仅仅是 5.

long double 的大小非常大。查看修改 x 字段对实现的影响,其中 xy 的尾数的 LSB 对齐,并且通过 [=11= 修改时并集的其他位不受影响], 你需要 print the value with much higher precision.

However, the reverse doesn't really apply

在小端系统上(多字节值的最低有效字节位于最低地址),int 将对应于 long double 的尾数的最低有效位.您必须非常精确地打印 long double 才能看到 int 对那些无关紧要的数字的影响。

在大端系统上,比如 Power PC 机,情况会有所不同:int 部分将与 long double 的最高有效部分对齐,与符号位重叠,指数和最重要的尾数位。因此 x 的变化会对观察到的浮点值产生巨大影响,即使只打印了几个有效数字。但是,对于 x 的小值,该值似乎为零。

在 PPC64 系统上,程序的以下版本:

int main(void)
{
    union myUnion a;
    a.y = 3.2;
    int i;
    for (i = 0; i < 1000; i++) {
      a.x = i;
      printf("%d -- %.2Lf\n", a.x, a.y);
    }
    return 0;
}

只打印

1 -- 0.0
2 -- 0.0
[...]
999 - 0.0

这是因为我们正在创建一个全为零的指数字段,从而产生接近于零的值。然而,初始值 3.2 完全被破坏了;它不仅让最低有效位变得混乱。

这只影响尾数的后半部分。它不会对您打印的数字量产生任何明显的影响。但是,打印 64 位数字时可以看出差异。

此程序将显示不同之处:

#include <stdio.h>
#include <string.h>
#include <ctype.h>

union myUnion
{
    int x;
    long double y;
};

int main()
{
    union myUnion a;
    a.y = 3.2;
    a.x = 5;
    printf("%d\n%.64Lf\n", a.x, a.y);
    a.y = 3.2;
    printf("%.64Lf\n", a.y);
    return 0;
}

我的输出:

5
3.1999999992549419413918193599855044340074528008699417114257812500
3.2000000000000001776356839400250464677810668945312500000000000000

根据我对 80 位 long double 格式的了解,这会覆盖尾数的一半,不会使结果产生太大偏差,因此打印出的结果比较准确。

如果你在我的程序中这样做了:

a.x = 0;

结果应该是:

0
3.1999999992549419403076171875000000000000000000000000000000000000
3.2000000000000001776356839400250464677810668945312500000000000000

只是略有不同。

Mohit Jain、Kaz 和 JL2210 发布的答案提供了很好的见解来解释您的观察和进一步调查,但请注意 C 标准不保证此行为:

6.2.6 Representations of types 6.2.6.1 General

6   When a value is stored in an object of structure or union type, including in a member object, the bytes of the object representation that correspond to any padding bytes take unspecified values. The value of a structure or union object is never a trap representation, even though the value of a member of the structure or union object may be a trap representation.

7   When a value is stored in a member of an object of union type, the bytes of the object representation that do not correspond to that member but do correspond to other members take unspecified values.

因此,无法保证答案中描述的行为,因为可以通过设置 int x 成员来修改 long double y 成员的所有字节,包括不属于的字节的 int。这些字节可以取任何值,y 的内容甚至可能是陷阱值,导致未定义的行为。

正如 Kaz 评论的那样,gcc 比 C 标准更精确:the documentation notes it as a common practice: The practice of reading from a different union member than the one most recently written to (called type-punning) is common. Even with -fstrict-aliasing, type-punning is allowed, provided the memory is accessed through the union type. This practice is actually condoned in the C Standard since C11, as documented in this answer: 。然而,在我阅读这个脚注时,仍然无法保证 y 的字节不是 x.

的一部分