Bootstrap

C++函数调用机制全解析:栈操作详解

函数调用过程栈的变化

函数调用过程中,栈的变化主要包括参数传递、返回地址保存、框架建立(栈帧),以及局部变量存储。以下是调用过程的详细步骤:

  1. 参数入栈
    调用者函数把被调函数所需要的参数按照与被调函数的形参顺序相反(从右到左)的顺序压入栈中。

  2. 保存返回地址
    调用者函数使用 call 指令调用被调函数,call 指令将下一条指令的地址(返回地址)压入栈中。

  3. 栈帧建立
    在被调函数中,首先保存调用者函数的栈底地址(push ebp),然后将当前栈顶地址保存为新的栈底(mov ebp, esp)。

  4. 局部变量入栈
    在新的栈帧中,从 ebp 开始存放被调函数的局部变量和临时变量,通常从高地址到低地址依次存放。

示例代码与注释

我们将通过一个具体的C++函数调用实例来展示这些步骤。

代码示例
#include <iostream>

void callee(int a, int b) {
    // 栈帧建立
    int local_variable = a + b;  // 局部变量
    std::cout << "Sum: " << local_variable << std::endl;
}

void caller() {
    int x = 10;
    int y = 20;
    callee(x, y);  // 调用函数
}

int main() {
    caller();
    return 0;
}

栈变化的解释

  1. 调用者函数 (caller)

    • 参数压栈
      callee(x, y) 调用时,首先将参数 y 压栈,然后是参数 x

      push y  ; 参数2
      push x  ; 参数1
      
    • 保存返回地址
      call callee 将当前指令之后的返回地址压栈,并跳转到 callee 函数。

      call callee  ; 保存返回地址并跳转
      
  2. 被调函数 (callee)

    • 保存调用者的栈底地址

      push ebp  ; 保存以前的栈底
      mov ebp, esp  ; 设置新的栈底
      
    • 为局部变量划分空间
      按需求调整栈指针为局部变量分配空间(例如 local_variable)。

      sub esp, X  ; 为局部变量分配空间
      
  3. 函数返回过程

    • 恢复调用者的栈帧

      mov esp, ebp  ; 恢复栈指针
      pop ebp  ; 恢复以前的栈底
      
    • 返回到调用者函数

      ret  ; 从栈中弹出返回地址并跳转
      

栈的布局示意图

以下是一般情况下,函数调用时栈的布局变化示意图:

调用者栈帧:
+--------------------------+
| 返回地址 (Call)          |  <-- 调用 callee 之前由 call 指令保存
| 参数 x (Caller)          |  <-- callee 参数1
| 参数 y (Caller)          |  <-- callee 参数2
+--------------------------+
| 调用者的栈底 ebp (Push)  |  <-- callee 中建立新的栈帧 
+--------------------------+
| 局部变量 (Callee)        |  <-- callee 中定义的局部变量
+--------------------------+

通过上述解释及示例代码,你可以更清楚地理解函数调用过程中栈的变化。

;