此问题在此处已有答案:
Why does the x86-64 / AMD64 System V ABI mandate a 16 byte stack alignment?(1个答案)
Why does this function push RAX to the stack as the first operation?(3个答案)
20天前关闭。
我使用的是Apache Explorer,注意到GCC和Clang在编译这个简单函数(Compiler Explorer)时会发出与堆栈相关的看似不必要的指令。
void bar(void);
int foo(void) {
bar();
return 42;
}
字符串
下面是编译的结果(也可以通过上面的链接在编译器资源管理器中看到)。-mabi=sysv
对输出程序集没有影响,但是我想排除ABI是导致奇怪程序集的原因。
// Expected output:
foo:
call bar
mov eax, 42
ret
// gcc -O3 -mabi=sysv
// Why is it reserving unused space in the stack frame?
foo:
sub rsp, 8
call bar
mov eax, 42
add rsp, 8
ret
// clang -O3 -mabi=sysv
// Why is it preserving a scratch register then moving it to another unused scratch register?
foo:
push rax
call bar@PLT
mov eax, 42
pop rcx
ret
型
为什么函数不使用栈,栈帧却被修改?
我觉得这特别奇怪,因为这似乎是一个特别容易的优化,主要编译器,如GCC和Clang执行时,与一个已知的ABI。
我有几个理论,但我希望得到一些澄清。
- 也许这样做是为了防止在
bar
递归调用foo
的情况下出现无限循环?通过在每次调用时消耗少量的堆栈空间,我们确保程序最终在耗尽堆栈空间时发生segfaults。也许clang也在做同样的事情,但它使用push
和pop
来允许在某些情况下更好的流水线?如果是这样的话,我可以使用任何CLI参数来禁用此行为吗?然而,这似乎不是问题,因为call
在x86-64上无论如何都会将rip
推送到堆栈。 - 也许有一些怪癖的C或AMD 64系统V ABI,我不知道?
- 也许我想多了,奇怪的汇编只是寄存器/堆栈优化不好的结果。也许在编译过程中的某个时候堆栈被使用了,但是在使用被优化之后,它无法删除堆栈上的值。
1条答案
按热度按时间uurity8g1#
对中。
call
指令将8个字节压入堆栈(返回地址),因此优化后的函数再调整8个字节,以确保堆栈指针与16字节对齐。我相信这是ABI的一项要求,以确保128位SSE寄存器值可以溢出到自然对齐的地址,这对于避免性能命中或故障非常重要,具体取决于CPU配置。并且/或者SSE指令可以用于从适当地址进行优化的块移动。
clang和gcc的情况实际上是相同的--您并不真正关心向堆栈槽中写入了什么,或者更新了哪个volatile寄存器,只关心调整了堆栈指针。