【问题标题】:Initialization of a union in C在 C 中初始化联合
【发布时间】:2015-06-05 08:16:36
【问题描述】:

我遇到了这个关于 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;
}

【问题讨论】:

  • 你得到的意外输出是什么?
  • @Peter Mortensen 您的编辑颠倒了问题的含义!应该是The output is supposed to be 0 2. I dont' get why!
  • @edc65 没错。我试图解决这个问题,但我建议的编辑stackoverflow.com/review/suggested-edits/8336900 被拒绝了。如果你也想试试,说不定你的运气会比我好……
  • 请注意,printf() 语句的输出会有所不同,具体取决于底层硬件架构是大端还是小端。输出可能是(假设是 32 位或 64 位架构)(小端序)“0 2”或(大端序)“0 0”。

标签: c initialization unions


【解决方案1】:

我将假设您使用一个小端系统,其中 sizeof int4 bytes (32 bits) 并且 sizeof a char1 byte (8 bits),并且其中整数以二进制补码形式表示。 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

【讨论】:

  • 谢谢阿琼。我只是没有考虑二进制表示。现在我懂了。 :)
  • 访问y 是否超出其界限,但仍在联合内部(例如y[3])是未定义的行为,类似于正常的数组访问?
  • @hexafraction AFAIK 除非 sizeof(int) 3 * sizeof(char), y[3] 是合法的,因为它只是 *(y + 3)y + 3 是工会占用的合法地址。
  • @hacks 不,这不是问题所在。可能您查看了已编辑的问题。问题不是很清楚。但我认为它是 为什么以下代码的输出应该是 0 2 ?我认为 OP 在这里的评论证明了我的假设。
【解决方案2】:

为联合分配的内存是联合中最大类型的大小,在本例中为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. endianesssizeof(int) 很重要,因系统而异。

PS:union中两个成员占用的内存是一样的。

【讨论】:

  • 取决于字节顺序。
  • 试试这个:union a z={0x12345678}; printf("\n%x %x",z.y[0],z.y[1]); 输出会更有示范性。
  • @WernerHenze:确实,它只在小端系统上打印0 2
  • 值得一提的是,这个赋值是有效的,因为联合体的两个成员在内存中占据了相同的位置
  • @Quirliom 取决于 sizeof(int)。大端系统上的 32 位 int 将产生 0 0 作为输出。
【解决方案3】:

标准是这样说的

6.2.5 类型:

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

编译器只为最大的成员分配足够的空间,这些成员在该空间内相互重叠。在您的情况下,内存分配给 int 数据类型(假设为 4 字节)。线

union a z = {512};

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

对此的内存表示将取决于机器架构。在 32 位机器上,它会像(将最低有效字节存储在最小地址中——Little Endian

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

或like(将最高有效字节存储在最小地址中--Big Endian

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

z.y[0] 将访问地址 0x1000 的内容,z.y[1] 将访问地址 0x1001 的内容,这些内容将取决于上述表示。
您的机器似乎支持 Little Endian 表示,因此 z.y[0] = 0z.y[1] = 2 和输出将是 0 2

但是,您应该注意,第 6.5.2.3 节的脚注 95 指出

如果用于读取联合对象内容的成员与上次用于在对象中存储值的成员不同,则该值的对象表示的适当部分是重新解释为新类型中的对象表示,如 6.2.6 中所述(有时称为“类型双关”的过程)。 这可能是一个陷阱表示

【讨论】:

    【解决方案4】:

    联合的大小是由容纳它的单个元素的最大大小得出的。所以,这里是int的大小。

    假设它是 4 bytes/int 和 1 bytes/char,我们可以说:sizeof union a = 4 bytes

    现在,让我们看看它实际上是如何存储在内存中的:

    例如,联合的一个实例a,存储在2000-2003:

    • 2000 -> int x, y[0] 的最后(第 4 个/最低有效/最右边)字节

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

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

    • 2003 -> int x 的第一个字节(最高有效位)

    现在,当你说 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。

    【讨论】:

    • 是基于十六进制解释还是二进制解释的输出。因为在这两种情况下似乎都有可能?
    • 我已经提到 0x 来表示按照正常约定的十六进制。没有什么是二进制的。你能说说哪一行令人困惑吗?
    【解决方案5】:

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

    union a z;
    z.x = 512;
    

    【讨论】:

      猜你喜欢
      • 2014-03-23
      • 2012-07-18
      • 2021-10-23
      • 2015-01-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-09
      相关资源
      最近更新 更多