对于下面的C代码,来自Compiler Explorer的GCC x86-64 10.2发出了我在下面进一步粘贴的程序集。
一条指令是subq $40, %rsp
。问题是,如何从中减去40个字节%rsp
不会使堆栈未对齐?我的理解是:
call foo
,堆栈是对齐的16个字节;call foo
在堆栈上放置一个8字节的返回地址,因此堆栈未对齐;pushq %rbp
在foo
开始时,在堆栈上又放置了8个字节,因此它又得到了16个字节的对齐;subq $40, %rsp
。结果,减少%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
16字节对齐的目的是使函数可以在当前级别以下的任何级别调用,如果它们需要对齐的本地变量,则不必担心对齐其堆栈。
如果没有ABI保证,则需要此功能的每个函数都必须向and
堆栈指针提供一些值以确保其正确对齐,例如:
and %rsp, $0xfffffffffffffff0
但是,在这种特殊情况下,没有理由没有必要这样做-bar()
函数是叶子的,这意味着编译器完全了解其级别或更低级别的任何对齐要求(它没有局部变量,并且不调用任何函数,因此没有要求)。
该foo()
函数在下面也没有要求,因为它调用的唯一是bar()
。它还似乎在确定自己的本地人也不需要这种级别的对齐。
即使bar()
或是foo()
从即时翻译单元外部调用的(也可以是,因为它们没有被标记static
),但这并不会改变不需要对齐的事实。
例如,如果bar
是在单独的翻译单元中,或者在无法确定不需要对齐的情况下调用了其他函数,情况将有所不同。
那将意味着gcc
不会完全了解其对齐要求。而且,的确,如果您bar
在Godbolt中注释掉了定义行(有效地隐藏了定义),您将看到该行的变化:
// int bar(int i) { return i; }
--> subq $48, %rsp ; no longer $40
顺便说一句,尽管在这种情况下16字节对齐在技术上不是必需的,但我认为这可能会使gcc
使用System V AMD64 ABI的主张无效。在该ABI中似乎没有任何允许这种偏离的内容,文本(PDF)指出(略作解释,并加粗体):
输入自变量区域的末尾应在16(如果
__m256
在堆栈上传递,则为32 )字节边界对齐。换句话说,该值%rsp + 8
是始终16(或32)时控制转移到函数入口点的倍数。堆栈指针%rsp
始终指向最新分配的堆栈帧的末尾。
尽管以已知的方式在这种情况下不会造成问题,但以使观察到的行为兼容的任何方式进行解释似乎没有什么余地。
是否有人认为足够重要而不必担心,超出了此答案的范围,我对此不作任何判断:-)
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句