当前位置: 首页 > news >正文

【C语言】函数栈帧的创建和销毁

一、核心概念:什么是栈和栈帧?

  1. 栈 (Stack)

    • 它是内存中的一块连续区域,专门用于管理函数调用。

    • 它遵循 “后进先出” 的原则,就像一摞盘子,你总是取最上面的那个。

    • 有一个 栈指针 (SP - Stack Pointer) 寄存器,始终指向栈的顶部。

    • 在x86架构中,栈的生长方向是 从高地址向低地址 扩展的。

  2. 栈帧 (Stack Frame)

    • 也叫活动记录,是栈中为单个函数调用所分配的一块内存区域。

    • 每一个函数调用都会在栈上创建一个属于自己的栈帧。

    • 当函数调用结束时,其对应的栈帧会被销毁。

    • 它包含了该函数执行所需的所有信息,如局部变量、参数、返回地址等。

二、主角寄存器

在深入过程之前,先认识两个关键寄存器(以x86-32位为例):

  • esp (Extended Stack Pointer)栈顶指针,始终指向系统栈的顶部。

  • ebp (Extended Base Pointer)栈底指针,又称帧指针,指向当前函数栈帧的底部。函数内部通过ebp的固定偏移来访问参数和局部变量。

可以把ebpesp想象成一个画框的上下边框,它们框出了当前函数栈帧的范围

三、调用过程

第一阶段:函数栈帧的创建(函数调用开始时)

假设我们有如下代码,main函数调用add函数。

int add(int a, int b) {int result = a + b;return result;
}int main() {int a = 10;int b = 20;int ret = add(a, b);return 0;
}

main函数执行到int ret = add(a, b);这一行时,栈帧的创建过程如下:

步骤 1:参数压栈 (从右向左)
  • 编译器通常以从右向左的顺序将函数参数压入栈中。

    1. 将参数b的值(20)压入栈。

    2. 将参数a的值(10)压入栈。

  • 此时,esp栈顶指针会向上(低地址方向)移动,指向最后压入的参数a

注意:这一步有时也通过push指令完成,它会隐式地减少esp并存入数据。

步骤 2:调用函数,压入返回地址
  • 执行 call add 指令。这条指令做了两件事:

    1. 下一条指令的地址(即call add后面那条指令的地址,这里是int ret = ...的地址)压入栈中。这个地址就是返回地址,函数执行完后要回到这里。

    2. 跳转到add函数的代码开始处执行。

步骤 3:进入新函数,保存旧栈帧
  • 现在,CPU开始执行add函数体的代码。

  • add函数首先要建立自己的“地盘”(栈帧)。

    1. push ebp: 将调用者(main函数)的ebp 值压栈保存。这是为了在add函数返回时,能恢复main函数的栈帧。

    2. mov ebp, esp: 让ebp指向新的栈顶。此时,ebpesp指向同一个位置,这就是add函数栈帧的底部

步骤 4:为新栈帧分配空间
  • sub esp, XXh: 将栈顶指针esp向上(低地址)移动一段距离(XX是16进制数)。这段新开辟的空间就是用于存放add函数的局部变量等数据。比如这里的int result

至此,add函数的栈帧已经完全创建好了。此时栈的布局如下图所示:

高地址
...
----------------------------
|        参数 b (20)       | <-- main函数的栈帧
----------------------------
|        参数 a (10)       |
----------------------------
|     main的返回地址       |
----------------------------
|    保存的main的ebp      | <-- [ebp]指向这里 (add栈帧的底部)
----------------------------
|                          |
|   add函数的局部变量      |
|     (例如: result)       | <-- [esp]指向这里 (add栈帧的顶部)
|                          |
----------------------------
...
低地址

第二阶段:函数体内的操作

在创建好的栈帧内,函数可以自由访问它的数据:

  • 访问参数: 通过 [ebp + 8] 访问第一个参数a,通过 [ebp + 12] 访问第二个参数b

  • 访问局部变量: 通过 [ebp - 4] 等方式访问局部变量result

函数体内部的运算(a + b)就在CPU的寄存器中进行,然后将结果存入局部变量result的位置。

第三阶段:函数栈帧的销毁(函数返回时)

add函数执行到return result;时,开始销毁自己的栈帧。

步骤 1:返回值处理
  • 通常,函数的返回值会存放在 eax 寄存器中。所以会执行 mov eax, [ebp - 4],将result的值放入eax

步骤 2:恢复栈指针和基址指针
  • mov esp, ebp: 将esp移回ebp的位置。这一步直接回收了为局部变量分配的所有栈空间esp现在指向保存的旧ebp

  • pop ebp: 将栈顶(esp指向的值)弹出到ebp寄存器中。这个值正是之前保存的main函数的ebp。现在,ebp就恢复指向了main函数的栈帧底部。同时,esp会自动下移(pop指令的效果),指向返回地址

步骤 3:返回调用者
  • 执行 ret 指令。这条指令会:

    1. 将栈顶的返回地址弹出到指令指针寄存器eip中。

    2. CPU接着从eip指向的地址(即main函数中call add的下一条指令)开始执行。

步骤 4:清理栈上的参数
  • 此时,esp指向参数amain函数需要清理为add函数调用压入的参数。

  • 通常通过 add esp, 8 指令实现(因为两个int参数共8字节)。这条指令让esp向下移动,完全回到了调用add之前的位置。

至此,add函数的栈帧被完全销毁,仿佛从未存在过。栈的状态和main函数调用add之前一模一样。main函数可以继续执行后续代码,并从eax寄存器中取得add函数的返回值,赋给局部变量ret

四、总结与要点

阶段关键操作作用
创建参数压栈(从右向左)传递参数
call 指令压入返回地址确保函数能正确返回
push ebp / mov ebp, esp保存旧栈帧,建立新栈帧基线
sub esp, XX为局部变量分配空间
销毁mov eax, [ebp-4]将返回值存入eax
mov esp, ebp回收局部变量空间
pop ebp恢复调用者的栈帧基线
ret跳回调用处,继续执行
add esp, XX (在调用者中)清理参数空间

五、理解栈帧的意义

  1. 实现函数调用/返回机制:通过返回地址和保存的ebp,保证了函数能层层调用并正确返回。

  2. 隔离作用域:每个函数的局部变量都在自己的栈帧中,实现了变量的隔离,避免了命名冲突。

  3. 支持递归:递归的每一层调用都有自己的栈帧,互不干扰。

  4. 调试利器:调试器就是通过分析栈帧链(每个保存的ebp都指向上一个栈帧的底部)来生成调用堆栈(Call Stack)信息的。

http://www.dtcms.com/a/532035.html

相关文章:

  • 架构的尺度:从单机到分布式,服务端技术的深度演进
  • 优秀国内个人网站网址网站设计需要那些模块
  • 【ARM驱动】【FreeROTS移植到ARM驱动平台介绍】
  • BELLE中的表1
  • go-ethereum core之交易索引txIndexer
  • 描述对于营销型网站建设很重要飘红效果更佳信阳做网站 汉狮网络
  • 油猴脚本学习1——元数据头部
  • mysql 如何让事件执行
  • PantherX2 debain/armbian Jellyfin10.10.7升级10.11启动后无法监听端口8096的解决办法
  • 网站建设利弊中山币做网站公司
  • Kaleidoscope for mac 文件对比工具
  • LeetCode 1901.寻找峰值2
  • 沈阳建设网站费用北京网站优化软件
  • 【Android】【底层原理】深入解析SELinux模块
  • 阮一峰《TypeScript 教程》学习笔记——注释指令
  • 最好的开发网站建设价格app免费制作平台下载
  • 供应链进销存源码uniapp全开源ERP多仓库管理系统pc+app手机端
  • 生物化学Learning Track(15)酶活性的调节
  • 站酷设计网站官网入口下载国外手机设计网站
  • Rust 泛型与特性
  • GPT-1 技术报告
  • 中英双语 网站 模板wordpress返佣
  • 机器学习库的决策树绘制
  • k8s的calico出现ipset报错解决方法
  • SimpleDateFormat
  • 网站后期的维护和更新池州网站建设推广
  • 低空经济的实时神经系统:空地一体化音视频架构的技术演进
  • 更换MacbookAir固态硬盘,并用U盘安装MacOS操作系统
  • 创建一个简单的SpringBoot
  • 硅基计划2.0 学习总结 玖 图书管理系统 2.0复盘版(文字末尾源码可复制)