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



真是奇怪的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的"红区"有什么奇怪的关系吗?到底发生了什么?

你猜对了。这是一个"红色区域"。红色区域是从rsp-128到rsp的空间,它可以被函数用于局部变量和临时存储。这个空间不受中断和异常处理程序的影响。显然,红色区域会被函数调用破坏,因此,如果调用任何函数,则红色区域内不存在局部变量。

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

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

对于您的示例,没有必要使用红色区域,但是gcc倾向于对所有"叶子"函数使用它。

x86-64 ABI要求在堆栈指针之外有一个128字节的'红色区域',可以在不修改%rsp的情况下使用。在第一个例子中,main()是一个叶函数,所以编译器正在优化堆栈空间的使用——也就是说,没有函数调用,所以这个区域不会被覆盖。

相关内容

  • 没有找到相关文章

最新更新