我在理解堆栈的工作原理时遇到了问题。首先是我的小代码:
void func1 ( int z ) {
int i = 1;
}
int main ( ) {
func1 ( 89 );
return 0;
}
我正在使用: Ubuntu 16.04 64位, gcc版本5.4.0, gdb版本7.11.1。
我正在使用GDB进行调试,以了解编译器如何在堆栈上推送函数参数。
当我在RSP指向的位置检查堆栈时,我得到了这个:
(gdb) x/10xw $rsp
0x7fffffffdf20: 0xffffdf30 0x00007fff 0x00400525 0x00000000
0x7fffffffdf30: 0x00400530 0x00000000 0xf7a2e830 0x00007fff
0x7fffffffdf40: 0x00000000 0x00000000
当我打印出最新创建的变量的地址时,我得到了这个:
(gdb) p &i
$4 = (int *) 0x7fffffffdf14
当我打印出该函数的地址时,我得到了这个:
(gdb) p &z
$5 = (int *) 0x7fffffffdf0c
筹码越来越少。
所以我认为RSP
始终指向堆栈的顶部,这意味着当我调用此命令x/10xw $rsp
时,我能够看到函数中的所有变量,但我可以'从那里看他们。
此命令后的第一个地址高于变量z
的地址。因此,我猜测RSP
点不在堆栈顶部。
我还想知道,i
的地址高于z
的地址。
由于i
后来被推到了堆栈而不是z
,因此我认为i
的地址必须低于z。
我希望有人可以解释为什么会这样。
编辑:我找到了答案!
这是编译器的优化。在func1()
中,RSP
寄存器没有指向堆栈的“顶部”,因为它没有必要。如果在func1()
中调用了其他函数,则这是必要的。所以编译器看到了并且没有递减RSP
寄存器。
这是我在func1()
中没有函数调用的汇编程序代码:
0x00000000004004d6 <+0>: push rbp
0x00000000004004d7 <+1>: mov rbp,rsp
0x00000000004004de <+8>: mov DWORD PTR [rbp-0x14],edi
0x00000000004004e1 <+11>: mov DWORD PTR [rbp-0x4],0x1
0x00000000004004e8 <+18>: mov eax,0x0
0x00000000004004f3 <+29>: leave
0x00000000004004f4 <+30>: ret
所以你看不到SUB
要求递减RSP
。
现在来自func1()
的代码带有函数调用:
0x00000000004004d6 <+0>: push rbp
0x00000000004004d7 <+1>: mov rbp,rsp
0x00000000004004da <+4>: sub rsp,0x20
0x00000000004004de <+8>: mov DWORD PTR [rbp-0x14],edi
0x00000000004004e1 <+11>: mov DWORD PTR [rbp-0x4],0x1
0x00000000004004e8 <+18>: mov eax,0x0
0x00000000004004ed <+23>: call 0x4004f5 <func2>
0x00000000004004f2 <+28>: nop
0x00000000004004f3 <+29>: leave
0x00000000004004f4 <+30>: ret
因此,您可以看到SUB
要求递减RSP
。所以RSP
可以指向“顶部”。
答案 0 :(得分:1)
x86上的约定是堆栈向递减地址“向下”增长。
堆栈的“顶部”只是最近推送的位置;它不是基于地址的相对值。堆栈可以在地址空间中“向上”或“向下”增长 - 对于某些实现(例如链表),地址甚至不必是顺序的。
This page对图表有一个公平的解释。