gcc - 为什么 GCC 在分配一个没有后续函数调用的大数组时将错误的值减去堆栈指针?

标签 gcc assembly x86 stack red-zone

真的很奇怪的 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”有什么奇怪的关系吗?究竟发生了什么?

最佳答案

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

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

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

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

关于gcc - 为什么 GCC 在分配一个没有后续函数调用的大数组时将错误的值减去堆栈指针?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8337783/

相关文章:

c - 在 x86 中将 union 对齐到 16 字节边界。

c - 利用缓冲区溢出

assembly - 汇编器64b师

performance - 为什么 SSE 指令保留 YMM 寄存器的高 128 位?

c++ - 禁用赋值运算符优化的 GCC 选项是什么

c++ - gcc 在 + 运算符中缩小转换

使用 header 错误 header 交叉编译应用程序和 gcc

assembly - 如何使用 assembly(8086) 将 USB 驱动器的扇区加载到内存中?

c - 通过内联汇编交换 2 个指针

c++ - 这个电话去哪儿了?