程序如何知道要为堆栈上的局部变量分配多少space?

How do programs know how much space to allocate for local variables on the stack?

在这个简单的函数中,space分配给局部变量。然后,初始化变量并调用printf输出它们。

000000000040056a <func>:
  40056a:       55                      push   rbp                     ; function prologue
  40056b:       48 89 e5                mov    rbp,rsp                 ; function prologue
  40056e:       48 83 ec 10             sub    rsp,0x10                ; deallocating space for local variables
  400572:       8b 4d fc                mov    ecx,DWORD PTR [rbp-0x4] ; variable initialization
  400575:       8b 55 f8                mov    edx,DWORD PTR [rbp-0x8] ; variable initialization
  400578:       8b 45 f4                mov    eax,DWORD PTR [rbp-0xc] ; variable initialization
  40057b:       89 c6                   mov    esi,eax                 ; string stuff
  40057d:       bf 34 06 40 00          mov    edi,0x400634            ; string stuff
  400582:       b8 00 00 00 00          mov    eax,0x0                 ; return value 
  400587:       e8 84 fe ff ff          call   400410 <printf@plt>     ; printf()
  40058c:       c9                      leave                          ; clean up local variables, pops ebp
  40058d:       c3                      ret                            ; return to the address that was pushed onto the stack (by popping it into eip)

让我困惑的是这一行sub rsp,0x10。程序如何知道分配 0x10 字节?是猜测吗?程序是否事先解析过?

编译器知道是因为它查看了源代码(或者实际上是解析后的逻辑内部表示),并将必须分配堆栈的所有内容所需的总大小加起来 space为了。而且它还必须在 call 之前让 RSP 16 字节对齐,因为函数入口上的 RSP % 16 == 8。

因此,对齐是编译器保留的功能可能比实际使用的功能多的原因之一,而且编译器未优化的错误也会使其浪费 space:common for GCC to waste an extra 16 bytes,尽管这里没有发生这种情况。

是的,现代编译器在为它发出任何代码之前解析整个函数(实际上是整个源文件)。这就是提前优化编译器的意义所在,因此它是围绕着这样做而设计的,即使您进行调试构建也是如此。相比之下,Tiny C 编译器 TCC 是一次性的,并在其函数序言中留下一个位置,以便稍后返回并在到达源代码中的函数底部后填充任何总大小。请参阅 - 当该数字恰好为零时,那里仍然有一个 sub esp, 0。 (TCC 仅针对 32 位模式。)

相关:Function Prologue and Epilogue in C


在叶函数中,编译器可以在针对 x86-64 System V 时使用 RSP 下面的红色区域,避免保留尽可能多(或任何)堆栈的需要 space 即使有一些局部函数选择spill/reload。 (例如,任何未优化的代码。)另请参阅 Why is there no "sub rsp" instruction in this function prologue and why are function parameters stored at negative rbp offsets? 除了内核代码,或使用 -mno-red-zone.

编译的其他代码

或者在 Windows x64 中,调用者需要为他们的被调用者保留阴影 space space 在他们的 return 地址之上。但对于非叶函数,这意味着保留至少 32 个字节的影子 space 加上任何用于对齐或局部变量。例如参见 [​​=22=]

在 x86-64 以外的 ISA 的标准调用约定中,其他规则可能会发挥影响。


请注意,在 64 位代码中,leave 弹出 RBP,而不是 EBP,ret 弹出 RIP,而不是 EIP。

此外,mov ecx,DWORD PTR [rbp-0x4] 不是 variable initialization这是从未初始化的内存到寄存器加载。可能你在没有初始化器的情况下做了类似 int a,b,c; 的操作,然后将它们作为参数传递给 printf.