C语言中联合体的初始化

27

我在C编程语言的一道客观题中遇到了这个问题。以下代码的输出应该是0 2,但我不明白为什么。

请解释初始化过程。以下是代码:

#include <stdio.h>

int main()
{
  union a
  {
    int x;
    char y[2];
  };
  union a z = {512};
  printf("\n%d %d", z.y[0], z.y[1]);
  return 0;
}

你得到了什么意外的输出? - Bergi
@Peter Mortensen,您的编辑颠倒了问题的意思!应该是“输出应该是0 2。我不明白为什么!” - edc65
1
@edc65 没错。我试图修复它,但我的建议编辑 http://stackoverflow.com/review/suggested-edits/8336900 被拒绝了。如果你也想尝试一下,也许你会比我运气更好... - Fabio says Reinstate Monica
请注意,printf() 语句的输出结果取决于底层硬件架构是大端序还是小端序。假设使用 32 位或 64 位架构,则输出结果可能为(小端序)'0 2' 或(大端序)'0 0'。 - user3629249
5个回答

19
我假设你使用的是一个小端字节序的系统,其中int的大小为4个字节(32位)char的大小为1个字节(8位),并且整数以二进制补码形式表示。一个union的大小只有它最大成员的大小,并且所有成员都指向这个确切的内存区域。

现在,您将一个整数值512写入这个内存。

512的二进制表示为1000000000

或者以32位二进制补码形式表示:

00000000 00000000 00000010 00000000

现在将其转换为小端表示,你会得到:

00000000 00000010 00000000 00000000
|______| |______|
   |         |
  y[0]      y[1]

现在看一下上面的内容,当你使用 char 数组的索引访问它时会发生什么。

因此,y[0]00000000,即 0

y[1]00000010,即 2


谢谢Arjun。我只是没有考虑到二进制表示法。现在我明白了。 :) - Sumit Cornelius
访问联合体内部的 y,超出其边界(例如 y[3])是否会像普通数组访问一样产生未定义行为? - nanofarad
据我所知,除非 sizeof(int) < 3 * sizeof(char),否则 y[3] 是合法的,因为它只是 *(y + 3),而 y + 3 是由联合占用的合法地址。 - Arjun Sreedharan
@haccks 不,那不是问题。可能你看了修改后的问题。问题不是很清楚。但我假设问题是“为什么以下代码的输出应该是0 2?”,我认为OP在这里的评论证实了我的假设。 - Arjun Sreedharan

8

联合分配的内存大小是联合中最大类型的大小,在本例中为int。假设您系统上的int大小为2字节,则

512将变为0x200

表示形式如下:

0000 0010 0000 0000
|        |         |
------------------- 
Byte 1     Byte 0

在小端系统上,第一个字节是0,第二个字节是2

char在所有系统上都是一个字节。

因此,访问z.y[0]z.y[1]是按字节访问。

z.y[0] = 0000 0000 = 0
z.y[1] = 0000 0010 = 2

我只是告诉你内存分配和值的存储方式。您需要考虑以下几点,因为输出取决于它们。

要注意的事项:

  1. 输出完全取决于系统。
  2. 字节序sizeof(int) 很重要,这将在系统之间变化。

注:联合体中两个成员占用的内存相同。


10
根据字节顺序而定。 - Werner Henze
1
尝试以下代码: union a z={0x12345678}; printf("\n%x %x",z.y[0],z.y[1]); 输出将更加直观。 - Jabberwocky
@WernerHenze:确实,只有在小端系统上才会打印出“0 2”。 - dummydev
暂时不要点踩:我怀疑原帖作者不会从你的回答中理解发生了什么。目前为止,它只是向读者抛出了一些重要的流行语,而没有解释它们,多次提到了字节顺序的问题(你写了三次,但没有链接到一个解释它是什么的页面..),而且还完全没有结构化的顺序。 - Phillip
现在我明白了。谢谢大家。 - Sumit Cornelius
显示剩余6条评论

8
标准规定:

6.2.5 类型:

联合类型描述了一组有重叠非空成员对象,每个对象都有一个可选的名称和可能不同的类型。

编译器只为最大的成员分配足够的空间,在该空间内它们会互相覆盖。在您的情况下,内存被分配给 int 数据类型(假设为4字节)。这行代码:

union a z = {512};

将初始化联合体z的第一个成员,即x变为512。在二进制中,它表示为32位机器上的0000 0000 0000 0000 0000 0010 0000 0000

这个内存表示方式取决于机器架构。在32位机器上,它可能是这样的(将最低有效字节存储在最小地址中-- 小端序

Address     Value
0x1000      0000 0000
0x1001      0000 0010
0x1002      0000 0000 
0x1003      0000 0000

或者像(将最高有效字节存储在最小地址--大端模式

Address     Value
0x1000      0000 0000
0x1001      0000 0000
0x1002      0000 0010 
0x1003      0000 0000

z.y[0] 将会访问地址为 0x1000 的内容,而 z.y[1] 将会访问地址为 0x1001 的内容,这些内容将取决于上述表示方式。

看起来你的机器支持小端表示法,因此 z.y[0] = 0z.y[1] = 2,输出结果将为 0 2

但是,你需要注意,在第 6.5.2.3 节的脚注 95 中指出:

如果用于读取联合对象内容的成员与最后一次用于在对象中存储值的成员不同,则该值的对象表示的适当部分将被重新解释为新类型中的对象表示,如 6.2.6 中所述(有时称为“类型拼接”)。这可能会导致陷阱表示


1
该联合体的大小由其单个元素所需的最大大小确定。因此,这里是int的大小。
假设每个int占用4字节,每个char占用1字节,我们可以说:union a的大小=4字节
现在,让我们看看它实际上是如何存储在内存中的:
例如,联合体a的实例存储在2000-2003:
  • 2000-> int x,y [0]的最后(第4个/最不重要/最右边)个字节

  • 2001-> int x,y [1]的第3个字节

  • 2002-> int x的第2个字节

  • 2003-> int x的第1个字节(最重要)

现在,当你说z = 512时:
因为z = 0x00000200,
  • M[2000] = 0x00

  • M[2001] = 0x02

  • M[2002] = 0x00

  • M[2003] = 0x00

因此,当您打印 y[0] 和 y[1] 时,它将打印数据 M[2000] 和 M[2001],分别为十进制的 0 和 2。


1
输出是基于十六进制解释还是二进制解释?因为两种情况都很可能发生。 - Sumit Cornelius
我已经提到0x表示十六进制,符合常规约定。没有任何内容是二进制的。你能指出哪一行让您感到困惑吗? - shreyans800755

0

对于自动(非静态)成员,初始化与赋值相同:

union a z;
z.x = 512;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接