数调用与返回,一般来说也可以称作『过程』。
过程与栈
过程,存在以下三种机制:
- 转移控制:过程 A 调用过程 B,将程序计数器(PC)设置为过程 B 的起始地址;返回时,PC 设置为过程 A 调用过程 B 后面的指令的地址;
- 传递数据:过程 A 向过程 B 传递一个或多个参数,过程 B 返回一个值;
- 分配和释放内存:过程 B 需要为局部变量分配内存空间,返回前需要释放这些内存空间。
『分配和释放内存』则通过『栈』来实现。程序通过栈管理过程所需的内存空间,栈和 PC 记录着三种机制所需的信息。每一个过程生成一个『栈帧』。
栈中保存了各种函数调用时的信息,而代码段中存储运行所需的机器指令,并通过程序计数器 PC 记录运行过程中机器指令的内存地址。
栈帧则是一个函数调用过程中保存的各种信息,并通过基址指针%ebp
和栈指针%esp
确定其在栈中的范围。
①:
%rsp
通过汇编指令push
和pop
执行出入栈操作,入栈内存地址减小,出栈内存地址增大;②:
%rbp
指向的是保存过程 A 的栈帧的帧指针,同时与过程 B 的%rsp
标定栈帧范围;③:过程 A 在调用过程 B 时,通过寄存器最多传送 6 个参数,如果还有多的参数,通过栈传出;
④:过程 B 返回后,PC 通过返回地址,得到继续运行的指令地址。
实际上,如果参数个数少于 6 个,且不会调用其他过程,则过程 B 无需栈帧,局部变量可以保存在寄存器中。
调用过程
假设有过程 A 调用了过程 B,经过以下几个步骤:
过程 A 为调用者,过程 B 为被调用者。
调用者保存参数,参数少于 6 个,直接存入寄存器,第 7 个及更多的参数保存在调用者的栈帧中;
前 6 个参数分别保存在
%rdi
、%rsi
、%rdx
、%rcx
、%r8
、%r9
中;其余参数在栈帧中的位置见上图。调用者将调用指令的下一个指令的地址,作为返回地址,存出调用者栈帧;
被调用者将调用者的基址指针
%rbp
保存,以便返回后恢复调用者的栈帧范围;被调用者设置自己的基址指针和栈指针指向同一个位置;
如果需要分配给被调用者分配栈,则栈指针下移,最终基址指针和栈指针记录了被调用者的栈帧范围;
被调用者产生局部变量时,保存到栈帧中,栈与数据段和代码段的区别;
通过 PC 执行被调用者的机器指令。
被调用者执行完成后,销毁其栈帧:
- 栈指针回退到基址指针,基址指针读取旧的
%ebp
,恢复到调用者的栈帧范围; - 返回地址 pop 到 PC 中,获取调用前机器指令的地址;
- 完全恢复到调用前的状态,继续执行。