栈与函数调用惯例(又称调用约定)— 正篇
在前篇笔记的基础上,本文继续介绍栈与函数调用约定的相关内容。
1. 函数调用的栈帧结构
IA32程序用栈来实现函数调用。机器用栈来传递函数参数、保存返回地址、保存寄存器(即函数调用的上下文)及存储本地局部变量等。为单个函数调用分配的那部分栈称为栈帧(stack frame),栈帧的边界由2个指针界定:寄存器%ebp为帧指针(严谨的说法是,帧指针存放在%ebp中),指向当前栈帧的起始处,通常较固定;寄存器%esp为栈指针,指向当前栈帧的栈顶位置,当程序执行时,栈指针可以移动,因此大多数数据的访问都是相对于帧指针的。
下图给出了栈帧的通用结构。
结合前篇笔记介绍的进程虚拟地址空间和上图的栈帧结构,我们可以看到,在经典的操作系统(如各种类UNIX操作系统)中,栈总是向下增长的,压栈(push)时栈顶地址减小,弹栈(pop)时栈顶地址增大。另外还可以注意到,堆通常是向上增长的,但在Windows系统中,大部分堆由HeapCreate()产生,而HeapCreate系列函数却完全不遵照堆向上增长的规律。
下面根据函数调用中典型的栈帧结构,对函数调用栈的实现过程做如下描述:
若函数P(调用者caller)调用函数Q(被调用者,callee),则Q的参数存放在P的栈帧中