考虑以下通过alloca()
函数在堆栈上分配内存的玩具示例:
#include <alloca.h>
void foo() {
volatile int *p = alloca(4);
*p = 7;
}
使用 gcc 8.2 编译上述函数,-O3
得到以下汇编代码:
foo:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
leaq 15(%rsp), %rax
andq $-16, %rax
movl $7, (%rax)
leave
ret
老实说,我本来希望有更紧凑的汇编代码。
分配内存的 16 字节对齐
上面代码中的指令andq $-16, %rax
导致在地址和(包括两者)之间rax
包含(仅)16 字节对齐的地址。rsp
rsp + 15
这种对齐强制是我不明白的第一件事:为什么alloca()
将分配的内存对齐到 16 字节边界?
可能错过优化?
无论如何,让我们考虑一下我们希望分配的内存alloca()
是 16 字节对齐的。即便如此,在上面的汇编代码中,请记住call foo
,如果我们注意堆栈内部的状态,GCC 在执行函数调用时假定堆栈与 16 字节边界对齐(即 )foo()
就在推送rbp
寄存器之后:
Size Stack RSP mod 16 Description
-----------------------------------------------------------------------------------
------------------
| . |
| . |
| . |
------------------........0 at "call foo" (stack 16-byte aligned)
8 bytes | return address |
------------------........8 at foo entry
8 bytes | saved RBP |
------------------........0 <----- RSP is 16-byte aligned!!!
我认为通过利用红色区域(即无需修改)和已经包含16 字节对齐地址rsp
的事实,可以使用以下代码:rsp
foo:
pushq %rbp
movq %rsp, %rbp
movl $7, -16(%rbp)
leave
ret
寄存器中包含的地址rbp
是 16 字节对齐的,因此rbp - 16
也将对齐到 16 字节边界。
更好的是,新堆栈帧的创建可以被优化掉,因为rsp
它没有被修改:
foo:
movl $7, -8(%rsp)
ret
这只是错过的优化还是我在这里遗漏了其他东西?