函数和栈

ZaynPei Lv6

我们通过下面这张图来整体理解函数调用和栈的关系: alt text

这张图非常经典,它清晰地展示了计算机程序在执行函数调用时,内存中(Stack)的结构和工作原理。图中描述了一个场景:一个名为 DrawSquare 的函数在执行过程中,调用了另一个名为 DrawLine 的函数。

我们先理解下面两个概念: 函数调用栈 (Call Stack) 和 栈帧 (Stack Frame)。

  • 函数调用栈 (Call Stack):一块特殊的内存区域,遵循后进先出 (LIFO, Last-In, First-Out) 的原则。每当一个函数被调用,就会在栈顶为其分配一块内存;当函数返回时,这块内存会被释放。

  • 栈帧 (Stack Frame):每次函数调用时在栈上创建的这块专属内存区域,就称为一个栈帧。它包含了该次函数调用所需的所有信息。每个栈帧通常包含三个主要部分, 且它们按照以下顺序从栈底到栈顶排列:

    • 参数 (Parameters):存放调用者函数传递给被调用函数的值。在图中, Parameters for DrawSquare 是调用 DrawSquare 时传入的参数。Parameters for DrawLine 是 DrawSquare 函数调用 DrawLine 时传入的参数。

    • 返回地址 (Return Address): 这是至关重要的一部分。它存储了函数调用指令的下一条指令在内存中的地址。当被调用的函数执行完毕后,CPU 就通过读取这个返回地址,知道应该跳转回哪里继续执行调用者函数的代码。在图中, DrawLine 栈帧中的 Return Address 指向 DrawSquare 函数中调用 DrawLine 语句的下一行代码。

    • 局部变量 (Locals): 存放函数内部定义的局部变量。这些变量在函数开始执行时创建,在函数返回时被销毁,其生命周期与函数调用绑定。在图中, Locals of DrawLine 存放的是 DrawLine 函数内部声明的变量。

图中展示了两个栈帧,DrawLine 的栈帧位于 DrawSquare 的栈帧之上(在内存地址较低的一侧),这表明 DrawSquare 函数调用了 DrawLine 函数。

除此之外, 图中还标示了两个非常重要的 CPU 寄存器,它们是管理栈的关键。

  • 栈指针/栈顶指针 (Stack Pointer, SP)

    • 作用:始终指向栈的顶部 (top of stack)。在图中,它指向 DrawLine 栈帧中局部变量区域的末端

    • 行为:当函数分配局部变量或进行新的函数调用(压栈,push)时,SP 会向低地址方向移动(栈增长)。当函数返回(出栈,pop)时,SP 会向高地址方向移动(栈收缩)。

  • 帧指针 (Frame Pointer, FP)

    • 作用:也称为基址指针 (Base Pointer, BP)。它指向当前活动栈帧(即位于栈顶的, 最后被调用且正在执行的那个函数)的一个固定位置,通常是栈帧的底部(或返回地址和参数之间的边界)。

    • 意义:FP 提供了一个稳定的基准地址。在函数执行期间,SP 可能会因为局部变量的动态分配而移动,但 FP 保持不变。因此,程序可以通过 FP 以固定的偏移量来准确地访问参数(正向偏移)和局部变量(负向偏移),而不受 SP 移动的影响。

结合这张图,我们可以还原整个函数调用的动态过程:

  1. 调用 DrawSquare:DrawSquare 的参数被压入栈中。

    • 调用指令 call DrawSquare 执行,将返回地址(main 函数或其他调用者的下一条指令地址)压入栈中。

    • DrawSquare 函数开始执行,创建自己的栈帧,分配局部变量空间。FP 和 SP 指针更新,指向这个新栈帧。

  2. DrawSquare 调用 DrawLine (图示的状态):DrawSquare 将调用 DrawLine 所需的参数压入栈中

    • 执行 call DrawLine 指令,将返回地址(DrawSquare 中的下一条指令地址)压入栈中。

    • DrawLine 函数开始执行,在 DrawSquare 栈帧的上方创建自己的栈帧,并分配局部变量空间。

  3. FPSP 指针再次更新,指向最顶部的 DrawLine 栈帧。这正是图中捕捉到的瞬间。

  4. DrawLine 返回:DrawLine 函数执行完毕, 它的栈帧被销毁(出栈)。SPFP恢复到指向 DrawSquare 栈帧的状态。CPU 读取 DrawLine 栈帧中保存的返回地址,并跳转到该地址,回到 DrawSquare 中继续执行。

  5. DrawSquare 返回:DrawSquare 执行完毕,其栈帧被销毁,控制权返回给最初的调用者。

On this page
函数和栈