Linux将System V ABI用于x86-64(AMD64)架构;有关详细信息,请参见OSDev Wiki上的System V ABI。
这意味着堆栈长大了; 较小的地址在堆栈中“较高”。典型的C函数被编译为
pushq %rbp ; Save address of previous stack frame
movq %rsp, %rbp ; Address of current stack frame
subq $16, %rsp ; Reserve 16 bytes for local variables
; ... function ...
movq %rbp, %rsp ; \ equivalent to the
popq %rbp ; / 'leave' instruction
ret
为局部变量保留的内存量始终是16字节的倍数,以使堆栈对齐到16字节。如果局部变量不需要堆栈空间,则没有subq $16, %rsp
或类似的指令。
(请注意,返回地址和前一个%rbp
被压入堆栈的大小均为8个字节,总计为16个字节。)
当%rbp
指向当前堆栈框架时,%rsp
指向堆栈顶部。由于编译器知道函数之间%rbp
和%rsp
函数之间任何地方的区别,因此可以随意使用其中之一作为局部变量的基础。
堆栈框架只是本地函数的游乐场:当前函数使用的堆栈区域。
每当使用优化时,当前版本的GCC都会禁用堆栈框架。这是有道理的,因为对于用C编写的程序,堆栈帧对于调试最有用,而其他方面则没有什么用。(但是,您可以使用例如-O2 -fno-omit-frame-pointer
保留堆栈帧,同时启用其他优化。)
尽管相同的ABI应用于所有二进制文件,但是无论它们以哪种语言编写,某些其他语言的确需要使用堆栈框架来“展开”(例如,向当前函数的祖先调用者“抛出异常”);即“展开”堆栈帧,可以中止一个或多个功能并将控制权传递给某些祖先功能,而无需在堆栈上保留不必要的内容。
如果省略了堆栈框架(-fomit-frame-pointer
对于GCC),则函数实现本质上将变为
subq $8, %rsp ; Re-align stack frame, and
; reserve memory for local variables
; ... function ...
addq $8, %rsp
ret
因为没有堆栈帧(%rbp
用于其他目的,并且它的值永远不会被压入堆栈),所以每个函数调用仅将返回地址压入堆栈,这是一个8字节的数量,因此我们需要从中减去8%rsp
使其保持16的倍数。(通常,从中减去并加到的值%rsp
是8的奇数倍。)
函数参数通常在寄存器中传递。看到这个答案细节开始的ABI链接,但在短,整型和指针寄存器传递%rdi
,%rsi
,%rdx
,%rcx
,%r8
,和%r9
在与浮点参数%xmm0
到%xmm7
寄存器。
在某些情况下,您会看到rep ret
而不是rep
。不要混淆:rep ret
含义与ret
;完全相同;rep
尽管通常与字符串指令(重复指令)一起使用,但该前缀在应用于该ret
指令时没有任何作用。只是某些AMD处理器的分支预测变量不喜欢跳转至ret
指令,建议的解决方法是改用rep ret
那儿。
最后,我忽略了堆栈顶部上方的红色区域(地址小于的128个字节%rsp
)。这是因为它对于典型功能并不是真正有用:在正常的had-stack-frame情况下,您希望将本地内容放在堆栈框架中,以使调试成为可能。在省略堆栈框架的情况下,堆栈对齐要求已经意味着我们需要从中减去8 %rsp
,因此在该减法中包括局部变量所需的内存不会花费任何成本。