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

时间:2016-11-14 02:00:33

标签: 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() {
    func();
}

当我反汇编func函数时,x86(我不确定它是x86还是x86_64)代码是

->  0x100001140 <+0>:  pushq  %rbp
    0x100001141 <+1>:  movq   %rsp, %rbp
    0x100001144 <+4>:  subq   $0x10, %rsp
    0x100001148 <+8>:  leaq   -0x4(%rbp), %rdi
    0x10000114c <+12>: callq  0x100000f90               ; do_something(int*)
    0x100001151 <+17>: addq   $0x10, %rsp
    0x100001155 <+21>: popq   %rbp
    0x100001156 <+22>: retq   
    0x100001157 <+23>: nopw   (%rax,%rax)

据我所知,第一个push语句是将基指针推送到堆栈上的前一个函数调用,然后堆栈指针值被复制到基指针。但那么为什么要为堆栈保留16个字节呢?

这是否与某种方式的对齐有关?变量a只需要4个字节..

这个函数调用中lea指令到底是什么?它只是获取相对于基指针的整数的地址?在这种情况下,似乎是从基数4个字节(假设返回地址长4个字节,是堆栈中的第一个东西)

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

2 个答案:

答案 0 :(得分:2)

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

进入函数后,调用者的CALL指令在堆栈上推送了8个字节以存储返回地址。第一个PUSH指令再次将堆栈对齐到16,无需额外的修正。

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

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

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

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

请记住,您正在查看未经优化的代码。通常在优化器完成后不会留下任何内容,像这样的小函数几乎总是内联。所以这并没有教你实际运行的代码的实际知识。看看-O2代码。

答案 1 :(得分:0)

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

leaq (mem), reg

等同于以下

reg = &(*mem) = mem