为什么main在没有变量时初始化堆栈帧

为什么这个代码:

#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 

为什么编译器初始化一个堆栈帧只是为了以后它被销毁,而它是否曾经被使用过? 这肯定不会导致主函数外部的任何错误,因为我从不使用堆栈,所以我不会导致任何错误。 为什么这样编译?

在每个编译函数中都有这些步骤是编译器的“基线”,未经优化。 它在拆卸时看起来很干净,而且很有意义。 但是,编译器可以优化输出以减少没有实际效果的代码的开销。 您可以通过使用不同的优化级别进行编译来查看。

你得到的是这样的:

 .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给出了这个输出:

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

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

假设堆栈在16字节边界上对齐。 在推送了返回地址的情况下,这将留下另外8个字节,以便在函数调用之前到达边界。

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

  • 调试时
  • 在依赖于__builtin_frame_address ( 级别 )且级别 > 0的代码中

正如其他人已经指出的那样,编译器可能会在更高的优化级别上省略堆栈帧。
另请参阅: 如何让gcc的__builtin_frame_address与-O2一起使用?

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

生成代码来初始化所有函数的堆栈并不比只在必要时这样做复杂。 由于优化器无论如何都能够删除不必要的代码(并且它会在更多情况下执行此操作而不是简单的“这个函数是否具有任何局部变量?”检查),生成不必要的代码将不会产生任何影响在启用优化时(如果不是,则预计生成的代码将包含低效率)。

如果我们确实添加了“这个函数有没有任何局部变量?” 检查生成堆栈初始化代码的函数,我们将重新发明优化器已经执行的优化程度较低的版本,因此我们违反了单一责任原则并增加了复杂性。编译器的一部分,否则可能相对简单(与优化器相反,优化器无论如何都充满了复杂的算法)。