欢迎光临
我们一直在努力

【C语言深度解剖】函数栈帧的创建与销毁(超详细·图文版·适合面试/底层原理)

前言:
大家好,本篇文章带你彻底吃透 C 语言函数调用的底层:函数栈帧(Stack Frame)的创建、维护、参数传递、返回值、销毁全过程。
内容偏底层硬核,适合想搞懂:

  • 函数为什么能调用?

  • 局部变量为什么不能跨函数使用?

  • 栈溢出是什么?

  • 指针越界、野指针根源在哪?

  • 面试常问:函数调用栈原理

看完这篇,你对 C 语言的理解会直接上一个台阶。

一、前置知识(必须先懂)

  • 什么是栈(stack)
    • 栈是内存中一块专门用于函数调用的区域

    • 生长方向:从高地址 → 低地址(向下生长)

    • 遵循:后进先出 LIFO(最后调用的函数最先返回)

  • 两个关键寄存器(理解栈帧核心)
    • ebp / rbp:栈底基址指针,固定不动,标记当前函数栈帧底部

    • esp / rsp:栈顶指针,一直在变,指向栈最顶端

    • 栈帧 = ebp 到 esp 之间的这片空间

  • 关键指令
    • push :把数据压入栈,esp 减小(向低地址走)

    • pop :把数据从栈取出,esp 增大(向高地址走)

    • mov :数据赋值

    • call :调用函数,会把下一条指令地址压栈

    • ret :函数返回,从栈中弹出返回地址

    二、环境与测试代码

    我使用:

    • Windows VS2022

    • x86(32位,更容易观察)

    • 调试 + 反汇编
      在这里插入图片描述
      功能:
      main 调用 Add ,计算 a + b,返回结果并打印。

    三、栈帧创建与销毁 完整逐步骤讲解

    第一步:进入 main 函数

    任何函数执行前,都要先创建栈帧。

  • 保存上一个函数的 ebp
    在这里插入图片描述
    • 将调用 main 的函数的栈底指针压入栈

    • esp -= 4

  • 让 ebp = esp,确立新栈帧
    在这里插入图片描述
    • 现在 ebp 就是当前栈帧的底部

    • 从这一刻起,ebp 不再动

  • 为局部变量预留空间
    在这里插入图片描述
    • esp 向下移动,留出局部变量空间

    • 栈帧大小确定

    第二步:main 函数局部变量创建
    在这里插入图片描述
    对应汇编类似:
    在这里插入图片描述

    • ebp-4 → 变量 a

    • ebp-8 → 变量 b

    它们都在 main 的栈帧内部。

    第三步:准备调用 Add 函数(参数压栈)

    执行:
    在这里插入图片描述
    重点:C 语言参数压栈顺序:从右向左

  • 先压 b

  • 再压 a
    在这里插入图片描述
    每次 push,esp 都在减小。

  • 然后 call 调用函数
    在这里插入图片描述
    call 做两件事:

  • 把下一条指令地址压栈(返回地址)

  • 跳转到 Add 函数

  • 第四步:Add 函数栈帧创建

    进入 Add 后,和 main 一模一样的流程:

  • 保存 main 的 ebp
    在这里插入图片描述
  • 新栈帧:ebp = esp
    在这里插入图片描述
  • 为 Add 的局部变量 z 开辟空间
    在这里插入图片描述
    现在 Add 栈帧形成:
    • ebp:栈底

    • esp:栈顶

    第五步:Add 函数内部执行
    在这里插入图片描述
    x、y 在哪?
    在栈帧上方,ebp 往上的位置:

    • [ebp+8] → x(a 的值)

    • [ebp+12] → y(b 的值)

    执行:
    在这里插入图片描述

    • ebp-4 → z

    • z = x + y 完成

    第六步:函数返回(return z)
    在这里插入图片描述
    返回值不会通过栈返回,而是通过 eax 寄存器:
    在这里插入图片描述
    第七步:Add 栈帧销毁(核心!)

  • 回收局部变量空间
    在这里插入图片描述
    • esp 回到 ebp 位置

    • 局部变量 z 所在空间直接被放弃

    • 数据还在,但无效了

  • 恢复 ebp(回到 main 的栈底)
    在这里插入图片描述
    • 弹出之前保存的 ebp

    • ebp 重新指向 main 的栈底

  • ret 返回 main
    在这里插入图片描述
    • 从栈中弹出 call 保存的返回地址

    • 跳回 main 继续执行

    第八步:回到 main,清理参数
    在这里插入图片描述

    • 之前压入的两个参数 a、b 占用 8 字节

    • 直接移动 esp 销毁它们

    第九步:main 继续执行
    在这里插入图片描述
    eax 中的返回值存入 ret 变量。

    四、一张图总结整个流程(文字版示意图)
    在这里插入图片描述
    五、最核心结论(面试必背)

  • 每调用一个函数,就创建一个独立栈帧

  • ebp 固定栈底,esp 动态栈顶

  • 栈从高地址向低地址生长

  • 局部变量在栈帧内,函数结束即销毁

  • 销毁不是清零,是esp 移动,使用权回收

  • 参数压栈顺序:从右向左

  • 返回值通过 eax 寄存器 带回

  • 栈帧层层嵌套,形成 函数调用栈

  • 六、结语:

    函数栈帧是 C 语言进阶必过的一道坎。
    搞懂它,你看代码就不再是“表面文字”,而是内存里的真实运行逻辑。

    如果你觉得这篇文章对你有帮助,别忘了 点赞 👍 收藏 ⭐ 关注 📢

    赞(0)
    未经允许不得转载:171主机测评 » 【C语言深度解剖】函数栈帧的创建与销毁(超详细·图文版·适合面试/底层原理)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址