关于C - union中的union作为一种类型并且读作另一种类型的问题 - 是否已实现定义?
-
06-07-2019 - |
题
我正在阅读K& R中关于C的联合,据我所知,联合中的单个变量可以包含几种类型中的任何一种,如果某些东西存储为一种类型并且提取为另一种类型,则结果纯粹是实施定义。
现在请查看此代码段:
#include<stdio.h>
int main(void)
{
union a
{
int i;
char ch[2];
};
union a u;
u.ch[0] = 3;
u.ch[1] = 2;
printf("%d %d %d\n", u.ch[0], u.ch[1], u.i);
return 0;
}
输出:
3 2 515
这里我在 u.ch
中分配值,但是从 u.ch
和 u.i
中检索。它是实现定义的吗?或者我做的事情真的很傻?
我知道这对其他大多数人来说似乎很初学,但我无法弄清楚输出背后的原因。
感谢。
解决方案
这是未定义的行为。 u.i
和 u.ch
位于相同的内存地址。因此,写入一个并从另一个读取的结果取决于编译器,平台,体系结构,有时甚至是编译器的优化级别。因此, u.i
的输出可能并不总是 515
。
实施例
例如,我的机器上的 gcc
为 -O0
和 -O2
生成两个不同的答案。
-
因为我的机器具有32位小端架构,使用
-O0
,我最终将两个最低有效字节初始化为2和3,两个最高有效字节未初始化。所以联盟的内存看起来像这样:{3,2,garbage,garbage}
因此我得到的输出类似于
3 2 -1216937469
。 -
使用
-O2
,我像你一样得到3 2 515
的输出,这使得union memory{3,2,0, 0} 代码>。会发生什么是
gcc
使用实际值优化对printf
的调用,因此程序集输出看起来像是等效于:#include <stdio.h> int main() { printf("%d %d %d\n", 3, 2, 515); return 0; }
可以像在该问题的其他答案中解释的那样获得值515。从本质上讲,这意味着当
gcc
优化调用时,它选择了零作为未初始化联合的随机值。
醇>
写一个联盟成员并从另一个读取通常没有多大意义,但有时对于使用严格别名编译的程序可能很有用。
其他提示
这个问题的答案取决于历史背景,因为语言的规范随时间而变化。而这件事恰好是受变化影响的人。
你说你正在读K&amp; R.该书的最新版本(截至目前)描述了C语言的第一个标准化版本 - C89 / 90。在那个版本的C语言中,写一个联合成员并读取另一个成员是未定义的行为。不是实现定义(这是一个不同的东西),而是未定义的行为。在这种情况下,语言标准的相关部分是6.5 / 7.
现在,在C的演变的某个稍后阶段(应用技术勘误3的C99语言规范版本),使用联合进行类型惩罚突然变得合法,即写一个联盟成员然后读另一个成员。 / p>
请注意,尝试执行此操作仍可能导致未定义的行为。如果您读取的值对于您通读的类型无效(所谓的“陷阱表示”),则行为仍未定义。否则,您读取的值是实现定义的。
您的特定示例对于从 int
到 char [2]
数组的类型惩罚相对安全。在C语言中,将任何对象的内容重新解释为char数组总是合法的(同样,6.5 / 7)。
但事实并非如此。将数据写入union的 char [2]
数组成员,然后将其作为 int
读取,可能会创建陷阱表示并导致未定义的行为。即使您的char数组有足够的长度来覆盖整个 int
,也存在潜在的危险。
但是在你的特定情况下,如果 int
恰好大于 char [2]
,你读到的 int
将涵盖未初始化的区域超出数组的末尾,这又导致未定义的行为。
输出背后的原因是在您的机器上整数存储在 little-endian format:首先存储最不重要的字节。因此字节序列 [3,2,0,0]表示整数3 + 2 * 256 = 515。
此结果取决于具体实施和平台。
此类代码的输出将取决于您的平台和C编译器实现。您的输出让我觉得您在litte-endian系统(可能是x86)上运行此代码。如果您将515放入i并在调试器中查看它,您会看到最低位的字节为3,而内存中的下一个字节为2,它完全映射到您放入ch的内容。
如果你在big-endian系统上这样做,你可能(可能)得到770(假设16位整数)或50462720(假设32位整数)。
它取决于实现,结果可能因不同的平台/编译器而异,但似乎正是这样:
515二进制是
1000000011
填充零以使其为两个字节(假设为16位int):
0000001000000011
这两个字节是:
00000010 and 00000011
2
和 3
希望有人解释为什么他们会被逆转 - 我的猜测是,字符不会被颠倒,但是int是小端。
分配给union的内存量等于存储最大成员所需的内存量。在这种情况下,你有一个int和一个长度为2的char数组。假设int是16位而char是8位,两者都需要相同的空间,因此union被分配了两个字节。
当您为char数组分配三个(00000011)和两个(00000010)时,union的状态为 0000001100000010
。当您从此联合中读取int时,它会将整个事物转换为整数。假设 little-endian 表示LSB存储在最低地址,int读取来自union的将是 0000001000000011
,这是515的二进制文件。
注意:即使int是32位也是如此 - 检查 Amnon的回答