【发布时间】:2020-11-01 01:20:00
【问题描述】:
对于下面的 C 代码,来自Compiler Explorer 的 GCC x86-64 10.2 发出我在下面进一步粘贴的程序集。
一条指令是subq $40, %rsp。问题是,为什么从%rsp 中减去 40 个字节不会使堆栈错位?
我的理解是:
- 就在
call foo之前,堆栈是16字节对齐的; -
call foo在堆栈上放置了一个 8 字节的返回地址,因此堆栈未对齐; - 但是
pushq %rbp在foo的开始处又在堆栈上放置了8 个字节,因此它再次对齐了16 个字节; - 所以堆栈在
subq $40, %rsp之前对齐了 16 个字节。因此,将%rsp减少 40 个字节一定会破坏对齐方式?
显然,GCC 在保持堆栈对齐方面发出了有效的程序集,所以我一定遗漏了一些东西。
(我尝试用 CLANG 替换 GCC,并且 CLANG 发出 subq $48, %rsp — 正如我直觉所期望的那样。)
那么,我在 GCC 生成的程序集中缺少什么?它是如何使栈保持 16 字节对齐的?
int bar(int i) { return i; }
int foo(int p0, int p1, int p2, int p3, int p4, int p5, int p6) {
int sum = p0 + p1 + p2 + p3 + p4 + p5 + p6;
return bar(sum);
}
int main() {
return foo(0, 1, 2, 3, 4, 5, 6);
}
bar:
pushq %rbp
movq %rsp, %rbp
movl %edi, -4(%rbp)
movl -4(%rbp), %eax
popq %rbp
ret
foo:
pushq %rbp
movq %rsp, %rbp
subq $40, %rsp
movl %edi, -20(%rbp)
movl %esi, -24(%rbp)
movl %edx, -28(%rbp)
movl %ecx, -32(%rbp)
movl %r8d, -36(%rbp)
movl %r9d, -40(%rbp)
movl -20(%rbp), %edx
movl -24(%rbp), %eax
addl %eax, %edx
movl -28(%rbp), %eax
addl %eax, %edx
movl -32(%rbp), %eax
addl %eax, %edx
movl -36(%rbp), %eax
addl %eax, %edx
movl -40(%rbp), %eax
addl %eax, %edx
movl 16(%rbp), %eax
addl %edx, %eax
movl %eax, -4(%rbp)
movl -4(%rbp), %eax
movl %eax, %edi
call bar
leave
ret
main:
pushq %rbp
movq %rsp, %rbp
pushq $6
movl $5, %r9d
movl $4, %r8d
movl $3, %ecx
movl $2, %edx
movl $1, %esi
movl $0, %edi
call foo
addq $8, %rsp
leave
ret
【问题讨论】:
-
有趣的发现。显然编译器认为
bar不需要堆栈对齐,所以它没有打扰。如果您将其设为extern int bar(int i);,则堆栈将正确对齐。 -
此外,如果您更改
bar,使其确实需要对齐,例如因为它自己调用另一个函数,编译器也会注意到这一点。 -
我对
-O0进行的优化感到好奇。显然,它是 ipa 堆栈对齐的一个功能,这是 GCC 中的默认设置。您可以在 GCC 版本 >= 9.0 中使用-fipa-stack-alignment和-fno-ipa-stack-alignment打开/关闭它。输出与 GCC 中选项 on/off 的比较:godbolt.org/z/a1YdjG -
函数是否可以从外部调用(“上面”)在这里并不真正相关。对齐要求保护 below 当前的函数,并且由于
gcc可以看到 foo 下面的所有函数都没有对齐要求,因此它认为没有必要。
标签: assembly stack x86-64 memory-alignment calling-convention