为什么main在没有变量的情况下初始化堆栈帧

Why does main initialize stack frame when there are no variables

为什么这个代码:

#include "stdio.h"
int main(void) {
    puts("Hello, World!");
}

决定初始化堆栈帧?这是汇编代码:

.LC0:
        .string "Hello, World!"
main:
        push    rbp
        mov     rbp, rsp
        mov     edi, OFFSET FLAT:.LC0
        call    puts
        mov     eax, 0
        pop     rbp
        ret

为什么编译器初始化堆栈帧只是为了稍后销毁它,而不是使用它?这肯定不会在主函数外部引起任何错误,因为我从不使用堆栈,所以我不会引起任何错误。为什么会这样编译?

在每个编译函数中都有这些步骤是编译器的 "baseline",未优化。它在拆卸时看起来很干净,并且有道理。但是,编译器可以优化输出以减少没有实际效果的代码的开销。您可以通过使用不同的优化级别进行编译来看到这一点。

你得到的是this:

.LC0:
  .string "Hello, World!"
main:
  push rbp
  mov rbp, rsp
  mov edi, OFFSET FLAT:.LC0
  call puts
  mov eax, 0
  pop rbp
  ret

这是在 GCC 中编译的,没有优化。

添加标志 -O4 给出 this 输出:

.LC0:
  .string "Hello, World!"
main:
  sub rsp, 8
  mov edi, OFFSET FLAT:.LC0
  call puts
  xor eax, eax
  add rsp, 8
  ret

您会注意到这仍然会移动堆栈指针,但它会跳过更改基指针,并避免与之相关的耗时内存访问。

假定堆栈在 16 字节边界上对齐。随着 return 地址被压入,在函数调用之前还需要减去另外 8 个字节以到达边界。

编译器以最不复杂的方式生成未优化的代码是很常见的(或者至少是最不复杂的方式,不会导致代码太糟糕以至于优化器无法修复它)保持代码简单并坚持单一职责原则(从某种意义上说,使代码更高效是优化器的工作)。

为所有函数生成初始化堆栈的代码比只在必要时这样做更简单。由于优化器无论如何都能够删除不必要的代码(并且在更多情况下它会比简单的 "does this function have any local variables?" 检查更有效),只要启用优化,生成不必要的代码就不会产生任何影响(如果不是,则预计生成的代码将包含低效率)。

如果我们确实向生成堆栈初始化代码的函数添加 "does this function have any local variables?" 检查,我们将重新发明一个优化器已经执行过的功能较弱的优化版本,因此我们将违反单一职责原则并增加编译器部分的复杂性,否则可能相对简单(与优化器相反,它充满了复杂的算法)。

堆栈帧使得在运行时检查调用堆栈成为可能。这很有用:

正如其他人已经指出的那样,编译器可能会在更高的优化级别上忽略堆栈帧。
也可以看看: How do you get gcc's __builtin_frame_address to work with -O2?