C++ 为什么堆栈帧上保留的空间比x86中需要的空间多

C++ 为什么堆栈帧上保留的空间比x86中需要的空间多,c++,assembly,x86-64,disassembly,C++,Assembly,X86 64,Disassembly,参考以下代码 #include <iostream> using namespace std; void do_something(int* ptr) { cout << "Got address " << reinterpret_cast<void*>(ptr) << endl; } void func() { int a; do_something(&a); } int main() {

参考以下代码

#include <iostream>
using namespace std;

void do_something(int* ptr) {
    cout << "Got address " << reinterpret_cast<void*>(ptr) << endl;
}

void func() {
    int a;
    do_something(&a);
}

int main() {
    func();
}
我知道第一个push语句将基指针推送到堆栈上的上一个函数调用,然后将堆栈指针值复制到基指针。但是为什么要为堆栈保留16个字节呢

这与对齐有什么关系吗?变量
a
只需要4个字节

另外,
lea
指令在这个函数调用中到底做了什么?它只是获取整数相对于基指针的地址吗?在本例中,它似乎与基址相差4字节(假设返回地址的长度为4字节,并且是堆栈上的第一个内容)


其他架构似乎保留了超过16个字节,并在堆栈帧的基础上存储了其他内容。

根据x86-64 ABI,在调用子例程之前,堆栈必须对齐16个字节

leaq (mem), reg
相当于以下内容

reg = &(*mem) = mem

这是x64代码,请注意rsp寄存器的用法。x86代码使用esp寄存器。x64 ABI最重要的实现细节是堆栈必须始终与16对齐。实际上,正确运行64位代码并不是必需的,但对齐保证确保编译器可以安全地发出SSE指令。它们的操作数需要16字节对齐才能快速。此代码段中没有实际使用的代码,但它们可能位于
dou\u something

在输入函数时,调用方的CALL指令已在堆栈上推送8个字节以存储返回地址。第一条PUSH指令将堆栈再次对齐到16,无需进行其他更正

然后,它创建堆栈帧来存储
变量。虽然只需要4个字节,但仅将rsp调整4并不足以提供必要的对齐。因此它选择下一个合适的值16。额外的12个字节只是没有使用

LEA指令是实现
&a
的非常方便的指令。LEA=加载有效地址=“获取的地址”。这里的计算不是特别复杂,当您使用类似于
&array[ix]
的东西时,它会变得更加复杂。如果数组元素大小为1、2或4字节长,那么单LEA仍然可以完成这项工作,这是非常常见的

-4是
a
变量从堆栈帧开始的偏移量。需要4个字节来存储
int
,编译器实现。请记住,堆栈向下增长,因此它不是0

然后它只是进行函数调用,rdi寄存器用于传递x64 ABI中的第一个参数。然后通过重新调整rsp再次破坏堆栈帧并恢复rbp


请记住,您看到的是未优化的代码。通常,在优化器使用它之后,这些都不会留下,像这样的小函数几乎总是内联的。因此,这并没有教你多少实际运行的代码的实用知识。查看-O2代码。

是的,您答对了。它用于aligment,
lea
&
操作符,但它与另一个方向的返回地址无关。它是
-4
,因为
sizeof(int)
4
,局部变量低于
rbp
(而且编译器决定在分配的16个字节的最后4个字节中分配它)。请注意,在64位模式下,返回地址实际上是8字节。@Jester我很难弄清楚lea指令到底在做什么,我不想猜测,因为我觉得这以后会适得其反。你能解释一下参数是如何传递的以及堆栈在这个函数调用中有什么吗?参数是在
rdi
中传递的,这就是为什么使用
leaq-0x4(%rbp),%rdi
。从
rbp
向上推送
rbp
,然后将返回地址推送到
main
。从
rbp
向下,您有一个局部变量
a
,后跟12个未使用的字节。
func
的返回地址将放在该地址下。@Jester如果这与对齐有关,那么为什么要保留16个字节?8字节不合适吗?ABI需要16字节对齐(以使SSE对齐的指令更容易)。使用“x86”作为包含长模式、32位兼容/传统模式和16位实模式的通用术语非常常见。这不是OP所做的,但我觉得说“x86使用ESP”似乎很奇怪,尤其是在一个非Windows/MSVC的问题上。OP使用AT&T语法和x86-64系统V ABI(RSI中的第一个参数)。gcc/Linux通常使用i386与amd64(或x86-64)来区分32位与64位x86。x64几乎只在Windows/Microsoft术语中使用。无论如何,i386 System V ABI多年来也需要16B堆栈对齐,因此这不是x86-64特有的。(中的ABI链接。)这是一种强烈的错觉,因此16位代码仍然相关。由完成家庭作业有困难的学生创建。我并不热衷于做任何事情来促进这种错觉。@PeterCordes 16位代码很有趣!目前正在使用我的80286保护模式操作系统。
reg = &(*mem) = mem