为什么要这段代码:
#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
为什么编译器仅初始化堆栈帧,以便以后销毁它,而不会使用它呢?这肯定不会在主函数的外部引起任何错误,因为我从不使用堆栈,因此不会引起任何错误。为什么用这种方式编译?
答案 0 :(得分:2)
在每个已编译函数中具有这些步骤的是未优化的编译器的“基准”。它在拆卸时看起来很干净,很有意义。但是,编译器可以优化输出以减少没有实际效果的代码的开销。您可以通过使用不同的优化级别进行编译来查看。
您得到的就像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字节边界上对齐。按下返回地址后,将剩下另外8个字节要减去,以到达函数调用之前的边界。
答案 1 :(得分:1)
通过堆栈框架,可以在运行时检查调用堆栈。这很有用:
正如其他人已经指出的那样,编译器可以在更高的优化级别上省略堆栈框架。
也可以看看:
How do you get gcc's __builtin_frame_address to work with -O2?
答案 2 :(得分:0)
对于编译器而言,以尽可能最简单的方式(或至少不会导致不会导致代码如此糟糕以至于优化器无法修复它的代码的最复杂的方式)生成未优化的代码是很常见的代码简单,并遵循“一责制”原则(从某种意义上说,使代码更高效是优化器的工作)。
生成代码以初始化所有函数的堆栈要比仅在必要时进行复杂得多。由于优化程序无论如何都可以删除不必要的代码(并且比起简单的“此函数是否有任何局部变量?”检查会在更多情况下这样做),因此生成不必要的代码将不会有任何效果。启用优化功能后(如果未启用优化功能,则预计生成的代码将包含无效性)。
如果确实添加了“此函数是否有任何局部变量?”检查生成堆栈初始化代码的函数,我们将重新发明优化程序已经执行过的功能较弱的优化版本,因此我们将违反“一责制”原则,并增加了该函数的复杂性否则可能相对简单的编译器部分(与优化器相对,后者仍然充满了复杂的算法)。