为啥在分配没有后续函数调用的大数组时,GCC 会向堆栈指针减去错误的值?

Posted

技术标签:

【中文标题】为啥在分配没有后续函数调用的大数组时,GCC 会向堆栈指针减去错误的值?【英文标题】:Why does GCC subtract the wrong value to the stack pointer when allocating a big array with no subsequent function calls?为什么在分配没有后续函数调用的大数组时,GCC 会向堆栈指针减去错误的值? 【发布时间】:2012-01-10 09:18:21 【问题描述】:

真是奇怪的 gcc 怪癖。看看这个:

main()  int a[100]; a[0]=1; 

产生这个程序集:

   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 81 ec 18 01 00 00    sub    $0x118,%rsp
   b:   c7 85 70 fe ff ff 01    movl   $0x1,-0x190(%rbp)
  12:   00 00 00 
  15:   c9                      leaveq 
  16:   c3                      retq

堆栈的顶部显然是 400,因为它是一个 100 * 4 的数组。因此,当它写入第一个条目时,它会执行 rbp - 400(“b”行)。好的。但是为什么它从堆栈(第'4'行)指针中减去280?那不是指向数组的中间吗?

如果我们之后添加一个函数调用,gcc 会做正确的事:

b() 
main()  int a[100]; a[0]=1; b(); 

产生这个程序集:

0000000000000000 <b>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   c9                      leaveq 
   5:   c3                      retq   

0000000000000006 <main>:
   6:   55                      push   %rbp
   7:   48 89 e5                mov    %rsp,%rbp
   a:   48 81 ec 90 01 00 00    sub    $0x190,%rsp
  11:   c7 85 70 fe ff ff 01    movl   $0x1,-0x190(%rbp)
  18:   00 00 00 
  1b:   b8 00 00 00 00          mov    $0x0,%eax
  20:   e8 00 00 00 00          callq  25 <main+0x1f>
  25:   c9                      leaveq 
  26:   c3                      retq 

在这里,它正确地减去了 400(行 'a')。

为什么添加函数调用时会发生变化? gcc 只是懒惰,因为没关系而没有做对吗?发生了什么?显然,这只发生在为 x86_64 编译时,而不是为普通 x86 编译时。这与 x86_64 的“redzone”有什么奇怪的关系吗?究竟发生了什么?

【问题讨论】:

你为什么担心没有任何效果的代码,从代码中可以看出!?此外,您的(第二个)示例应该在涉及堆栈的地方使用一些调用约定,因为在您的示例中(在堆栈上)不涉及参数传递。旁注:我讨厌 AT&T 组装 :) 也许是因为好奇?或者这不是你书中的正当理由吗?顺便说一句,我发现答案很有启发性 【参考方案1】:

你的猜测是正确的。这是一个“红色区域”。红色区域是从 rsp-128 到 rsp 的空间,它可以被函数用于局部变量和临时存储。该空间不受中断和异常处理程序的影响。很明显,红区是被函数调用破坏的,所以如果调用任何函数,红区就不能有局部变量。

红色区域只能用于 64 位 Linux、BSD 和 Mac。它在内核代码中不可用。

它可用于优化空间,因为在红色区域中,您可以使用短指令引用最多 512 字节的局部变量,仅基于 rsp 和 ebp。没有红色区域,只有 384 字节可用。所有超出此限制的局部变量都可以使用更长的代码或额外的寄存器来访问。

对于您的示例,不需要使用红色区域,但 gcc 更喜欢将它用于所有“叶子”功能。以这种方式实现编译器更容易。

【讨论】:

关于红色区域在内核代码中不可用:实际上,Linux内核不尊重它。【参考方案2】:

x86-64 ABI 要求在堆栈指针之外有一个 128 字节的“红色区域”,无需修改 %rsp 即可使用。在第一个示例中,main() 是一个叶函数,因此编译器正在优化堆栈空间的使用 - 即没有函数调用,因此该区域不会被覆盖。

【讨论】:

以上是关于为啥在分配没有后续函数调用的大数组时,GCC 会向堆栈指针减去错误的值?的主要内容,如果未能解决你的问题,请参考以下文章

为啥这个自定义分配器的析构函数在 GCC/MSVS 的 stdlib 中被调用两次

调用方法并将返回值分配给数组时,为啥C#在调用方法时使用数组引用?

调用函数时为啥形参的值不能传给实参

C语言中,数组名作为函数参数,属于啥传递,为啥?

为啥使用字符串初始化没有 const 的数组时 gcc 不给出警告?

为啥 gcc 4.x 在调用方法时默认为 linux 上的堆栈保留 8 个字节?