我有一些用gcc编译的C代码:
int main() {
int x = 1;
printf("%d\n",x);
return 0;
}
我已经通过gdb 7.9.1运行它并为main提出了这个汇编代码:
0x0000000100000f40 <+0>: push %rbp # save original frame pointer
0x0000000100000f41 <+1>: mov %rsp,%rbp # stack pointer is new frame pointer
0x0000000100000f44 <+4>: sub $0x10,%rsp # make room for vars
0x0000000100000f48 <+8>: lea 0x47(%rip),%rdi # 0x100000f96
0x0000000100000f4f <+15>: movl $0x0,-0x4(%rbp) # put 0 on the stack
0x0000000100000f56 <+22>: movl $0x1,-0x8(%rbp) # put 1 on the stack
0x0000000100000f5d <+29>: mov -0x8(%rbp),%esi
0x0000000100000f60 <+32>: mov $0x0,%al
0x0000000100000f62 <+34>: callq 0x100000f74
0x0000000100000f67 <+39>: xor %esi,%esi # set %esi to 0
0x0000000100000f69 <+41>: mov %eax,-0xc(%rbp)
0x0000000100000f6c <+44>: mov %esi,%eax
0x0000000100000f6e <+46>: add $0x10,%rsp # move stack pointer to original location
0x0000000100000f72 <+50>: pop %rbp # reclaim original frame pointer
0x0000000100000f73 <+51>: retq
据我了解,push %rpb
将帧指针推送到堆栈上,因此我们可以稍后使用pop %rbp
检索它。然后,sub $0x10,%rsp
清除堆栈上10个字节的空间,以便我们可以在其上放置内容。
稍后与堆栈的交互通过内存寻址将变量直接移入堆栈,而不是push
将它们放到堆栈中:
movl $0x0, -0x4(%rbp)
movl $0x1, -0x8(%rbp)
为什么编译器使用movl而不是push来将这些信息放到堆栈中?
在内存地址之后引用寄存器是否也将该值放入该寄存器?
答案 0 :(得分:4)
现代编译器在函数开头移动一次堆栈指针并在结尾处移回它是很常见的。这允许更有效的索引,因为它可以将内存空间视为内存映射区域而不是简单堆栈。例如,突然发现没有用的值(可能是由于优化的快捷操作符)可以忽略,而不是强迫一个人将它们从堆栈中弹出。
也许在更简单的日子里,有一个使用推送的性能原因。使用现代处理器,没有任何优势,因此没有理由在编译器中使用push / pop来设置特殊情况。它不像编译器编写的汇编代码是可读的!
答案 1 :(得分:2)
虽然Cort是正确的,但这种做法显然是在堆栈上分配空间还有另一个重要原因。根据ABI,函数调用必须找到16字节对齐的堆栈。不是每次都需要从函数调用堆栈,而是调整堆栈以便正确对齐 first 然后修改可能具有的值,这通常更容易,更有效。被推上了它。
因此,堆栈完全针对局部变量空间进行了调整,但也进行了调整,以便为调用标准库提供正确的堆栈对齐。
答案 2 :(得分:0)
我不是汇编程序或编译器的权威,但是当天我和MASM一起玩过,并且在调试生产C ++问题的同时花了大量时间在WinDbg上。
我认为你的问题的答案是因为它更容易。
push / pop指令写入和读取堆栈,但它们也会在处理堆栈时对其进行修改。 C / C ++编译器对其所有局部变量使用堆栈。它通过将堆栈指针移动到保存所有局部变量所需的确切字节数来实现,并且在您输入函数时就这样做了。
在读取和写入之后,所有这些变量都可以在函数的任何地方完成,也可以通过简单地使用mov指令多次完成。如果你看一下纯组件,你可能会质疑为什么在你可以完成两个推送指令时,使用mov将两个值复制到该空间中时,为什么在堆栈中创建一个洞。
但是从编译器作者的角度看它。输入函数和为局部变量分配堆栈的过程是单独编码的,并且与读/写这些变量的过程完全分离。