3.7.1 运行时栈
3.7.1 运行时栈
C 语言过程调用机制的一个关键特性(大多数其他语言也是如此)在于使用了栈数据结构提供的后进先出的内存管理原则。在过程 P 调用过程 Q 的例子中,可以看到当 Q 在执行时,P 以及所有在向上追溯到 P 的调用链中的过程,都是暂时被挂起的。当 Q 运行时,它只需要为局部变量分配新的存储空间,或者设置到另一个过程的调用。另一方面,当 Q 返回时,任何它所分配的局部存储空间都可以被释放。因此,程序可以用栈来管理它的过程所需要的存储空间,栈和程序寄存器存放着传递控制和数据、分配内存所需要的信息。当 P 调用 Q 时,控制和数据信息添加到栈尾。当 P 返回时,这些信息会释放掉。
如 3.4.4 节中讲过的,x86-64 的栈向低地址方向增长,而栈指针 %rsp 指向栈顶元素。可以用 pushq 和 popq 指令将数据存入栈中或是从栈中取出。将栈指针减小一个适当的量可以为没有指定初始值的数据在栈上分配空间。类似地,可以通过增加栈指针来释放空间。
当 x86-64 过程需要的存储空间超出寄存器能够存放的大小时,就会在栈上分配空间。这个部分称为过程的栈帧(stack frame)。

图 3-25 给出了运行时栈的通用结构,包括把它划分为栈帧。当前正在执行的过程的帧总是在栈顶。当过程 P 调用过程 Q 时,会把返回地址压入栈中,指明当 Q 返回时,要从 P 程序的哪个位置继续执行。我们把这个返回地址当做 P 的栈帧的一部分,因为它存放的是与 P 相关的状态。Q 的代码会扩展当前栈的边界,分配它的栈帧所需的空间。在这个空间中,它可以保存寄存器的值,分配局部变量空间,为它调用的过程设置参数。大多数过程的栈帧都是定长的,在过程的开始就分配好了。但是有些过程需要变长的帧,这个问题会在 3.10.5 节中讨论。通过寄存器,过程 P 可以传递最多 6 个整数值(也就是指针和整数),但是如果 Q 需要更多的参数,P 可以在调用 Q 之前在自己的栈帧里存储好这些参数。
为了提高空间和时间效率,x86-64 过程只分配自己所需要的栈帧部分。例如,许多过程有 6 个或者更少的参数,那么所有的参数都可以通过寄存器传递。因此,图 3-25 中画出的某些栈帧部分可以省略。实际上,许多函数甚至根本不需要栈帧。当所有的局部变量都可以保存在寄存器中,而且该函数不会调用任何其他函数(有时称之为叶子过程,此时把过程调用看做树结构)时,就可以这样处理。例如,到目前为止我们仔细审视过的所有函数都不需要栈帧。