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