【C初阶】第九篇——函数栈帧的创建与销毁

简介: 【C初阶】第九篇——函数栈帧的创建与销毁

前言


在我们前期的学习编程过程中,我们会遇到许多诸如:"局部变量是如何创建的?","形参和实参有什么关系?","为什么局部变量是随机值?","函数调用时如何调用的?"等等的一些问题,今天就带大家一起了解vs2013编译器下的函数栈帧的创建于销毁,当然不同的编译器底下的这个过程有略微的差异,但大体上相同.

函数栈帧的创建与销毁


了解两个寄存器ebp和esp


ebp和esp两个寄存器中存放的是地址,这两个地址是用来维护函数栈帧的。每一次函数调用都要在栈区开辟一块空间,而这块空间就是由这两个寄存器来维护的,一个ebp(栈底指针)和一个esp(栈顶指针)。我们拿一串简单的代码来做演示

#include <stdio.h>
int Add(int x, int y)
{
  int z = 0;
  z = x + y;
  return z;
}
int main()
{
  int a = 10;
  int b = 20;
  int c = 0;
  c = Add(a, b);
  printf("%d\n", c);
  return 0;
}

image.png

函数栈帧创建与销毁的具体过程


main函数的函数栈帧


开辟由于栈区是在高地址先开辟空间,后向低地址开辟空间,随着main函数中的函数创建,esp和ebp会向上移动,维护新创建的函数栈帧.

image.png

image.png

main函数在__mainCRTStartup内部被调用,__mainCRTStartup在mainCRTStartup函数内部被调用。main函数中返回值为0是返回给mainret这个变量。

我们来分析一下上面那串代码的函数栈帧的创建与销毁的过程:

image.png

先是Push一下ebp,push就是压栈的意思,也就是在函数栈顶压入ebp这个元素.

087b5073b44d4132bbd308e65a6ab34f.png

297b42cf0bbd498d8da009a2da76b70a.png

接下来就是mov ebp和esp,mov就是把后一个值赋给前一个值得意思.

5e19f20fe2a541fcb60d7493add0e835.png

两个寄存器得内容相同,指针指向同一个地址.

image.png

接下来就是sub意思就是减去,esp指针内容地址都减去0E4h,esp指针就向上移动,两个指针共同来维护为main函数新开辟得函数栈帧.

image.png

image.png

压进去后,

image.png

接下来就是lea,lea是加载的意思,把后面的值加载到edi中.

image.png

一个word是两个字节,dword是双字节的意思, 也就是4个字节,每次操控4个字节的内容,共9h次.将从edi地址开始向下到esp这些内容全部改成0xcccccccc.

image.png

这块内容就全部初始化为0xcccccccc内容,所以这就是为什么局部变量未初始化是为随机值的原因.

以上的所有内容就是为main函数准备的栈帧.

变量的创建


image.png

接下来就是执行代码,在a的位置放入0Ah(10),b的位置放入14h(20),c的位置放入0。

image.png

Add函数栈帧的创建与销毁


以下就是传参的过程

image.png

image.png

image.png

call指令是调用函数且记住下一条指令的地址,先进入函数,方便之后出函数可以找到call的下一条指令

image.png

现在进入了Add函数,你会发现,这里与main函数栈帧的准备过程有一些相似之处,其实遮羞就是Add函数栈帧的准备过程。接下来我们再来分析一下:

image.png

image.png

接下来又是创建临时变量的过程了。

image.png

接下来就是传参的过程了,找到之前压入的值,然后赋值给形参x和y。

image.png

其实在调用函数的时候,就已经通过压栈的形式 来进行传参,传参先行。这也证实了形参是实参的一份临时拷贝这句话。

最后就是返回z,把ebp-8的值赋给eax。eax是一个寄存器,函数销毁时,这个寄存器还是在的

image.png

接下来就是pop几次,弹出的意思,esp往下移动,这样Add函数就销毁了。

image.png

回到main函数


image.png

最后执行完call指令,通过之前压入的call的下一条指令的地址,回到原来的地方。这就是为啥当时保存call指令的下一条指令。出的去,也要回的来。

image.png

相关文章
|
存储 编译器 C语言
【深入理解函数栈帧:探索函数调用的内部机制】
【深入理解函数栈帧:探索函数调用的内部机制】
445 0
|
3月前
|
域名解析 网络协议 Linux
Linux网络基础完全指南(小白也能看懂的网络入门教程)
本教程系统讲解Linux网络基础,涵盖IP地址、子网掩码、网关、DNS等核心概念,介绍ifconfig、ip、ping等常用命令及网络配置文件的使用方法,助力掌握Linux网络配置技能。
483 117
|
3月前
|
存储
原码,反码,补码
原码是二进制的直观表示,符号位决定正负;反码用于解决负数计算问题,负数按位取反;补码在反码基础上加1,解决跨0误差。计算机中所有数据存储与运算均采用补码形式,可准确处理正负数运算,并能多表示一个特殊值-128。
536 5
|
4月前
|
存储 人工智能 运维
一行代码实现智能异常检测:UModel PaaS API 架构设计与最佳实践
阿里云 UModel PaaS API 发布:通过 Table + Object 双层抽象,屏蔽存储差异、自动处理字段映射与过滤条件,让每一个实体都成为一个‘可调用的对象’,真正实现‘以实体为中心’的智能可观测。
950 146
|
4月前
|
缓存 算法 Java
线程池
本文深入剖析了Java线程池的核心原理,涵盖ThreadPoolExecutor与ScheduledThreadPoolExecutor的实现机制。通过分析线程池的创建、任务调度、执行流程及内部结构(如Worker、阻塞队列、延迟队列等),揭示了其如何高效管理线程资源、支持周期性任务调度,并探讨了Executors工具类中各类线程池的应用场景与区别,帮助开发者理解并合理使用线程池技术。
|
4月前
|
Java 调度
什么是分片广播任务
本文介绍XXL-JOB的分片广播机制,通过集群执行器动态分片处理任务。调度中心为每个执行器分配分片参数,实现任务并行处理,提升效率。适用于大数据量分布式场景,支持动态扩容,每台机器处理部分数据,显著降低耗时。开发时可通过`getShardIndex()`和`getShardTotal()`获取分片信息,灵活控制业务逻辑。
|
3月前
|
弹性计算 安全 Linux
2026年阿里云服务器镜像 Alibaba Cloud Linux 3.2104 LTS 64 位特性与适配场景解析
Alibaba Cloud Linux 3.2104 LTS 64 位镜像凭借对 ECS 的深度优化、CentOS 生态兼容性、长期安全支持,成为阿里云服务器的优选操作系统之一,尤其适合追求性能稳定、需要长期维护或从 CentOS 8 迁移的用户。在选择时,需根据业务场景(如是否需要快速启动、是否需等保合规)选择对应变种版本,并注意实例规格与镜像的兼容性。如需进一步了解配置细节或技术支持,可参考阿里云官方文档,确保系统部署符合业务需求。
|
4月前
|
存储 NoSQL Linux
2.4 Linux系统中的安装启动和连接
本文介绍在Linux系统部署单机MongoDB用于生产环境的完整步骤,包括下载、解压、目录配置、日志与数据路径设置、配置文件编写及服务启停方法。操作类似Windows,通过配置`mongod.conf`实现后台运行,支持命令行与图形工具连接,并提供防火墙处理与安全关闭服务方案,确保稳定运行。
|
4月前
|
Java Linux 开发工具
Linux
本文介绍如何在Linux系统上部署SpringBoot应用。内容涵盖项目打包、JAR文件上传、JDK安装与配置、应用启动及健康检查接口验证,助力快速完成Java应用的Linux环境部署。
 Linux