为什么GCC在不同作用域中为本地联合分配单独的堆栈空间?

7
请提供需要翻译的内容。
#include <stdlib.h>

#ifndef TRY
#define TRY struct
#endif

TRY testme
{
  int one;
  int two;
  char three;
  int four;
};

int
main (void)
{
  {
    volatile TRY testme one;

    one.one = 2;
    one.three = 7;
  }

  {
    volatile TRY testme twos;

    twos.one = 3;
  }

  {
    volatile TRY testme one;

    one.one = 4;
  }

  {
    volatile TRY testme twos;

    twos.one = 5;
  }

  {
    volatile TRY testme twos;

    twos.one = 6;
  }

  {
    volatile TRY testme twos;

    twos.one = 6;
  }

  return EXIT_SUCCESS;
}

在x86编译时(也就是说testme是一个结构体),编译器为main函数分配的栈大小为16字节。

$ gcc -g -O2 test.c -o test 
$ objdump -d ./test | ./checkstack.pl i386 | grep main
16 main

然而,如果定义了TRY为union(意味着testme是一个union),编译器为main分配的堆栈大小为32字节:

$ gcc -DTRY=union -g -O2 test.c -o test 
$ objdump -d ./test | ./checkstack.pl i386 | grep main

此外,在其他作用域中定义的结构/联合体的任何其他实例,在使用联合体时会产生更大的堆栈分配,但在使用结构体时不会扩大堆栈分配。
现在,这没有意义——联合体应该占用更少的堆栈空间,如果有的话,而不是与具有相同字段的结构体一样多!
似乎GCC即使在不同的范围内使用联合体时也将其视为同时使用,但对于结构体则不做同样的处理。
还有一些澄清:
1. volatile用于防止编译器优化赋值。去掉volatile并进行无优化编译会产生相同的结果。
2. 即使testme是一个具有联合体成员之一的结构体,也会观察到相同的行为。换句话说——只要结构体的一个成员是联合体,GCC就会为不同的堆栈分配分别分配堆栈。
3. 编译器是gcc版本4.4.3(Ubuntu 4.4.3-4ubuntu5),但其他架构的GCC版本显示了相同的行为。
4. checkstack.pl只是搜索objdump输出以查找用于分配堆栈(向堆栈指针的sub)的指令。
我的问题:
1. GCC为什么要这样做?这是个错误还是这种行为有原因?
2. 假设这不是一个错误,是否有一种方法可以解决这个问题,并强制GCC像联合体一样为结构体分配堆栈。
澄清:我的问题不是为什么结构体或联合体的大小似乎比其部分的大小要大。我理解原因是用于对齐的填充。我的问题是编译器即使在不同的作用域中定义了多个联合体实例,也会为它们分配多个堆栈帧,而对于具有相同字段的结构体确实不会这样做。
谢谢!

也许联合体在“-O2”优化下还没有被很好地优化?看一下生成的汇编代码。同时尝试禁用“strict-aliasing”。哦,还要尝试使用当前版本的GCC。 - Has QUIT--Anony-Mousse
1
你看过这个问题这个问题吗?这似乎是填充和对齐约束的问题。 - Coren
@Coren 我已经知道了,谢谢。我的问题不是为什么结构体的大小比其部分之和大。而是为什么GCC会为不同的联合实例在不同的堆栈区域分配内存,而对于结构体却不这样做。 - gby
如果您只执行一个块,那么分配了多少堆栈?无论如何,它都应该认识到它可以在每个块结束时重新使用堆栈内存。我能想象的唯一原因是编译器选择不这样做,是为了一次性初始化所有内容。所以6 * 16字节->每次重用16字节,6 * 4字节->初始化一次,由于它们非常小,因此在堆栈上保留6个实例。对于这个程序,使用4字节,堆栈管理可能比保留额外的4字节更昂贵。 - Has QUIT--Anony-Mousse
@Anony-Mousse 对于结构体,单个块或多个块的数量都是相同的 - 在这种特定设置中为16字节。对于联合体,随着块数的增加,其大小也会增加。放置20个块会得到200字节的堆栈等等。 - gby
显示剩余4条评论
2个回答

8

显然,至少已经尝试放宽gcc对于联合体的严格别名警惕。

您可能希望确保您编译的gcc源代码已应用了此补丁或等效补丁: http://codereview.appspot.com/4444051/


0

看起来是填充和基本默认大小定义的问题。

在32位中,内存映射如下: One(2字节) Two(2字节) Three(1字节) (1字节)填充 four(2字节)

总共- 8字节。

在64位中,它将是: One(4字节) Two(4字节) Three(1字节) (3字节填充) Four(4字节)

总共- 16字节。

如果将“int”更改为“short int”,则内存将会有所不同。


1
但是作为一个union,整个东西只需要4个字节,无论是32位还是64位。 - Aaron Digulla
我对单个结构体或联合体占用多少空间没有问题。我的问题是,GCC似乎为不同作用域中定义的联合体的不同实例分配不同的堆栈空间,尽管它不需要这样做,而且对于结构体也不会这样做。 - gby
即使是联合体,它也取决于接下来发生的事情。如果它在联合体数组内部,即使在64位系统下它只会占用32位。如果它是一个变量,在64位系统上它会被填充到64位。这不是范围的问题,而是“接下来发生的事情”的问题。 - גיא דפני

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