好久不用,很多内容都忘了。刚刚复习了一下,再写一遍:32位x86系统C函数调用时的内存分布。
每个C语言函数在运行过程中,在栈顶对应一个栈帧。想想堆栈是一个地址由上往下增长的线性内存(为什么要由上往下增长?后面说明),栈帧的顶端地址位于esp寄存器,栈帧底部地址由ebp寄存器保存(当然,这个说法不准确,ebp再往下还有这个函数的参数,后面详细说明)。结构如下所示(每行4字节):
00 临时变量2 <---- esp
04 临时变量1
08 局部变量2
0C 局部变量1
10 调用函数ebp指针值 <---- ebp
14 调用函数下一条执行指令地址
18 参数1
1C 参数2
可以看出,esp指向栈顶,ebp指向调用函数的ebp值: *esp = (first temp variable), *ebp = (last function's ebp),第一个参数是*(ebp+8),上一个函数中的返回指令是*(ebp+4)
在函数调用前,esp指向参数1(0x18),执行call命令后,esp指向调用函数下一条执行指令地址(0x14),被调用函数首先执行如下指令:
pushl %ebp movl %esp, %ebp
此时 esp=ebp=0x10。
ok,回到刚才的问题,为什么要从上往下想象地址空间。这种想象地址空间的能力非常重要,这个搞不好,在学汇编和读汇编时会比较郁闷。
一般我们想象一个栈的时候,要么从上往下增长,要么从下往上增长,或者从左往右、从右往左增长。不过一般push时我们感觉是“压进去”,pop时感觉是“弹出来”,这样从上往下增长的话,感觉比较怪异:往上面压进去,从上午往下弹出来。而水平压进去、弹出来的话,展示栈中的内容展示不了多少,因为栈中数字一般是四字节(对于32位系统)一个数据,水平写栈中内容的话可能就是如下的样子:
0x12345678 0x87654321 0x12345678 0x87654321 0x12345678 0x87654321
一行写不了几个数据,并且还看着比较不舒服。所以,栈从下往上涨是想起来最舒服的。
而内存空间为什么地址从上往下依次增大想象起来更舒服一些呢?首先,栈一般都是从大地址往小地址增长的,按照栈从下往上增长的方向,内存地址当然要从上往下增长了。另外,C语言中的结构(struct)都是从上往下写的,而一般上面的地址比下面的地址要小,更靠前,因此地址从上往下想时也更加符合平时看着的习惯。