引子
C语言的基本构成单位是函数,通过合理的组织、调用函数来完成一系列的目的。
我开始学习的时候就好奇调用函数(或者说调用过程)时到底发生了什么?
数据在内存中是如何组织的?
函数返回时如何准确到找到下一条将要执行的指令?
等等等一系列的疑问,了解了之后,豁然开朗,记录下来时常温习。
栈帧结构
IA32的程序使用堆栈支持过程的调用(函数的调用),在函数调用时会专门从堆栈中分出一块内存(称为帧)供函数使用。
传递给函数的参数由堆栈来保存,帧则负责存储寄存器的状态、局部变量的内存分配的相关任务。
如果说函数P调用函数Q,那么称P为调用者(caller),Q是被调用者(callee)。
根据上述规则, 堆栈会给Q分配帧,并且用两个指针(分别存储在 %ebp 和 %esp 中)指示帧的开始和结束的位置。
[图示caller代表P,callee代表Q]
Caller’s Framer:P是一个函数,会被其他过程调用,所以也会给它分配帧,但是我们不关心它,因为这里考虑的是P调用Q。注意P的帧分为了三个部分。
(1)自身使用区:保存P函数中的局部变量,寄存器等状态,是在Argument n
以上的蓝色部分。
(2)参数区:Argument 1--Aegument n
,这些都是将要传递给Q的参数,记得吗?参数是存储在caller的帧中的。
(3)返回地址区:即标注为Return address
的区域。它存储了当从Q返回后,P将要执行的下一条指令的地址。Callee’s Framer : Callee’s Frame一定是当前帧(Current’s Framer)。我们注意到Q的帧也是大体分为了三个部分。
(1)帧开始段 : %ebp 指向帧开始段的地址。该地址的内存单元存储的是之前 %ebp 指向的地址。在P调用Q之前, %ebp