C语言(函数)—函数栈帧的创建和销毁
目录
前言
补充知识
一、函数线帧是什么?
二、函数线帧的实现(举例说明)
两数之和代码
编辑两数之和 汇编代码分析
执行第一条语句
执行第二条语句
执行第三条语句
执行第四、五、六条语句
执行第七条语句
执行第八、九、十条语句
执行第十三条语句
执行第十四、十五条语句
执行第十六条语句
执行第十七条语句
执行第十七、十八条语句
执行第十九、二十条语句
进入Add函数里面
Add函数预开辟空间
创建z
相加
实际传参
返回z
弹出
回收没用空间,回到main函数
将数放进c中
三、总结
局部变量是怎么创建的,局部变量的创建?
为什么局部变量的值是随机值?
函数是怎么传参的?传参的顺序是什么样的?
形参和实参是什么关系?
函数调用结果是怎么返回的?
前言
这里补充一下函数线帧的创建和销毁,我们知道函数调用一次就会占用一次栈内存。每一次函数调用都会为本次函数调用分配内存空间(是在内存的栈区),为本次函数调用分配的内存空间叫做被称为这次函数调用的栈帧空间,函数栈帧的创建和销毁。
编译器越高级,那么 就越不容易发现在函数调用的过程中线帧的创建,具体细节取决于编译器的实现。新的编译器由于考虑各种各样的问题,所以封装的更加复杂,不容易分离出来函数栈帧创建的过程。
栈空间的使用是从高地址向地地址增长。
寄存器是集成到cpu上的,跟mian函数是没有关系的,是独立的,硬盘,内存,寄存器是相互独立的。
补充知识
我们知道计算机中有寄存器,而寄存器包括很多,例如eax,ebx,ecx,edx,ebp,esp等。
而ebp,esp这两个寄存器中存放的是地址,这两个地址是用来维护函数栈帧的。
每一个函数调用,都要在栈区上创建一个空间,而当调用哪个函数,esp和ebp就会维护哪个函数线帧。通常esp称为栈顶指针,sbp称为栈底指针。
当我们在调用main函数之前,会调用_tmainCRTStartup这个函数,这个函数内部调用了main函数,而这个函数又是被mainCRTStartup这个函数调用的。
其实在VS2013中,main函数也是被其他函数调用的。
一、函数线帧是什么?
函数栈帧是指在函数调用过程中,为了保存函数的局部变量、参数和执行上下文等信息而创建的一块存储区。
每次函数调用时,都会在栈上创建一个新的栈帧,用于存储该函数的局部变量、参数和执行上下文等信息。栈帧通常包括以下几个部分:
局部变量区域:用于存储函数内部定义的局部变量和临时变量等。
参数区域:用于存储函数的参数值。
返回地址:用于保存函数调用完成后的返回地址,以便能够返回到调用方继续执行。
上一级函数的栈帧指针:用于保存上一级函数的栈帧地址,以便能够返回到上一级函数。
其他上下文信息:如调用方的寄存器值、异常处理信息等。
函数栈帧的创建和销毁是由编译器和操作系统自动完成的,开发者一般无需手动管理。函数栈帧的创建和销毁按照函数调用的顺序,形成一个栈结构,因此也被称为调用栈或执行栈。在函数调用完成后,栈帧被销毁,栈指针回退到上一级函数的栈帧,继续执行上一级函数。这种递归结构的栈帧可以保证函数调用的嵌套和返回顺序的正确性。
二、函数线帧的实现(举例说明)
两数之和代码
#define _CRT_SECURE_NO_WARNINGS
#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;
}
我们通过用vs2022来进行讲解,例子用的上述代码+两数之和函数。
两数之和 汇编代码分析
我们通过F10来调试代码,之后在代码界面点击右键,选择反汇编,就会出现一个新页面,而这个页面就是图中右侧的,这是C语言对应的汇编代码。
把显示符号取消勾选,因为选上会显示符号名,而我们想要看其地址和布局,所以就去掉。
这时候里面就写的是符号的地址了,而不是符号名字了。
因为main函数是其它函数调用的,所以调用的__tmainCRTStartup已经分配好栈空间了,此时esp和ebp就会维护这个函数线帧(前提下面是高地址,上面是高地址):
执行第一条语句
意思是将ebp进行压栈,所以栈顶就多了一个元素(ebp),因为esp维护的是栈顶,所以esp指向往上移动了一步。内存就压进去一个ebp。
执行第二条语句
mov的意思就是把后者的值赋给前者,这里也就也是将esp的值赋给ebp,我们知道esp的值赋给了ebp,那么ebp的地址就指向esp,而esp又指向栈顶,ebp就指向了之前esp指的地方,所以就变成了下面:
执行第三条语句
sub是前者减去后者,也就是esp减去后面的数字,0E4h是八进制,esp减去这个数后值变小了,所以esp就指向了上面的某一位置,我们知道esp和ebp之间的就是函数的栈帧空间,所以他们俩之间的就是开辟出来main函数的预留空间,也就是main函数的栈帧。
执行第四、五、六条语句
分别压栈压进去三个元素,ebx,esi,edi,同时esp的值会往上走。每一个push,esp都会往上挪一下。
执行第七条语句
lea实际上是 load effecitive address(加载有效地址)的意思,这里面就是将后面的有效地址加载到edi中,相当于edi里面加上了一个地址。它实际上就似乎找了第三条语句的地址。
执行第八、九、十条语句
这三步真正有意义的是第三步,前两步是把后面的值放到前面的里面。而第三步则是从edi开始,将39h这么多个空间全部改成eax的内容,每一次初始化(dword个字节,一个4个字节)。实际就是将main函数预开辟的空间的全部数据改成0ccccccch。
自此,main函数的栈帧就已经准备好了。接下来才是操作的代码。
执行第十三条语句
这里就是将0Ah(10) 放到ebp-8这个地址的地方,也就是a的地方,这块地方就放了10
执行第十四、十五条语句
与第十三条语句一样差不多,都是赋值给对应地址。
这就可以总结出函数创建的时候局部变量的创建规律:
首先,创建这个函数的函数栈帧
之后找到一些空间把变量放进去
接下来就是调用函数,因为函数调用需要传参
执行第十六条语句
从这里我们可以看见后面这个地址与前面的有一个地址一样,这里就是b的地址,所以这里把b的值也就是20放进了eax.
执行第十七条语句
这里进行压栈,把eax压栈,放到栈顶。如下图:
执行第十七、十八条语句
这里与前两句一样,先把a的值存到ecx中,之后在把ecx进行压栈。
执行第十九、二十条语句
执行call指令,会调用一个地址,并且把地址压入栈中,而压入栈中的地址就是下一条指令的地址,也就是add的地址00521918(每次编译可能会不同)。
为什么要调用下一条指令的地址,因为call指令会进入add函数,但进入函数后还需要回来,回来怎么回来,就需要一个地址来进行返回,来回到call指令的下一条指令,再从这个地址往下执行命令。
进入Add函数里面
Add函数预开辟空间
再往下运行,就进入到了Add函数里面,而上面这些就是为了为Add函数预开辟线帧空间。
与之前开辟main函数的线帧空间一样。开辟之后就如上图所示。
创建z
在ebp-8的地址传入z,这里面放的是0
相加
我们知道ebp -8就是ecx的位置,ebp-12就是eax的位置,同时ecx里面是a的值,eax里面是b的值,所以给他们给他们命名为是a'和b'。
这里面吧ebp+8的值(10)加到eax里面去,所以eax就为10,之后吧ebp+0ch(12)的值(20)再加入eax中,这时候eax里面就为30,加起来之后再把eax的值放进ebp-8的地址里面去,而z恰好是ebp-8的地址,所以z就为30。
这里我们发现函数参数x,y并没有,而是通过调用指令进行传参,将形参进行push压栈,压到某一位置,参数是从右向左传的,当真的来到函数内部来调用这两个数相加的时候会发现,形参根本不是在函数内部创建的,而是回来找刚才调入进来传参传入的这个空间,压进去这个空间,上图a'就认为是x,b'就认为是y,也就是x+y,之后传入z的空间。
实际传参
实际传参是还没有调用这个函数的时候,参数a和b就已经传过去了,在函数栈帧中压入了两个参数(b和a),压进去之后,真正用函数内部的时候,其实是再找回之前压入栈中的这两个值,然后相加之后再给z。所以形参是实参的临时拷贝这句话得到了验证。
返回z
因为我们知道函数调用完后会销毁,同时z也会销毁,所以把ebp-8里的值也就是z的值30放进eax寄存器中,寄存器不会因为函数的销毁而销毁,所以放在这里就安全了,先保存起来。
弹出
pop就是弹出的意思,弹出一个esp就往下移动一个地址,之后就如下图了:
这时候发现有些空间没有用到,所以应该回收。
回收没用空间,回到main函数
这里将ebp的位置传入esp,用pop(把栈顶的元素 弹出来放到ebp里面去),因为此时ebp(main地址)存的是之前下面的ebp地址,所以ebp又回到了下面(之前的地方),而esp则指向下call指向下一条指令的地址,因为之前的ebp已经弹出了。
这样就回到了main函数里面,这样栈帧空间又是又esp和ebp开始维护。
现在栈顶元素是call指令下一条指令的地址
这条指令就是回到了call指向下一条指令的地址。再往下执行就是:
esp+8之后,esp就指向了如下图:
当指向这里的时候,就说明把ecx和eax就还给了操作系统。所以这时候形参的空间就还给了操作系统,释放了。
将数放进c中
这里将eax(计算的和)放进ebp-20h(c)里面去,这样返回值就带了回来。
至此,函数线帧的创建和销毁就结束了。
总图:
三、总结
局部变量是怎么创建的,局部变量的创建?
首先先分配好栈帧空间,然后在这个空间里面分配一些空间来创建局部变量
为什么局部变量的值是随机值?
因为这个随机值是我们自己放进去的,初始化后就把随机值覆盖了,这时候就不是随机值了
函数是怎么传参的?传参的顺序是什么样的?
当我们要调用这个函数的时候,其实函数还没有调用的时候,就已经把两个参数从右向左push压栈压进去了,当真正进入函数的时候,通过指针的偏移量来找到之前传入的这两个形参参数
形参和实参是什么关系?
形参是实参的一份临时拷贝,形参确实是在压栈的时候开辟的空间,它和实参只是值是相同的、空间是独立的,改变形参是不会影响实参的。
函数调用结果是怎么返回的?
之前就把call指令的下一个指令的地址就压进栈了,把ebp调用这个函数的上一个函数栈帧的ebp就存进去了,当我们函数调用完要返回时候,弹出edp,就可以找到原始(上一个函数调用的ebp),指针往下走就可以找到esp的地址,回到了mian函数。
记住call指令的下一个指令地址就可以回来进行下一个命令。
返回值通过寄存器来临时保存,之后进行返回。
相关文章:

C语言(函数)—函数栈帧的创建和销毁
目录 前言 补充知识 一、函数线帧是什么? 二、函数线帧的实现(举例说明) 两数之和代码 编辑两数之和 汇编代码分析 执行第一条语句 执行第二条语句 执行第三条语句 执行第四、五、六条语句 执行第七条语句 执行第八、九、十条语句 执行第十…...

点餐小程序实战教程20广告管理
目录 1 创建数据源2 添加轮播容器3 创建变量4 绑定变量5 预览应用总结 一般餐厅需要有一些宣传,在我们的点餐页面可以在顶部加载广告位。广告主要是用轮播图的形式进行展示,本节我们介绍一下如果显示广告。 1 创建数据源 打开控制台,点击应用…...

市场上几个跨平台开发框架?
跨平台桌面应用开发框架是一种工具或框架,它允许开发者使用一种统一的代码库或语言来创建能够在多个操作系统上运行的桌面应用程序。传统上,开发者需要为每个操作系统编写不同的代码,使用不同的开发工具和语言。而跨平台桌面应用开发框架通过…...
同步和异步、引用、变量声明、全局变量
同步和异步 如果计算机足够快,任何资源的访问速度都像Cache一样,没有异步的必要。 编程语言的同步和异步 越早期的编程语言,支持语言级别的异步越欠缺。 JS提供某些操作的同步和异步函数,例如文件读取,fs.readFile和fs…...

2024年10月份实时获取地图边界数据方法,省市区县街道多级联动【附实时geoJson数据下载】
首先,来看下效果图 在线体验地址:https://geojson.hxkj.vip,并提供实时geoJson数据文件下载 可下载的数据包含省级geojson行政边界数据、市级geojson行政边界数据、区/县级geojson行政边界数据、省市区县街道行政编码四级联动数据࿰…...

@RequestMapping对不同参数的接收方式
1、简单参数 1、参数名与形参变量名相同,定义形参即可接收参数,且会自动进行类型转换。 RequestMapping("/simple")public String simpleParam(String name,int age){String username name;int userAge age;System.out.println(username&…...
机器学习_KNN(K近邻)算法_FaceBook_Location案例(附数据集下载链接)
Facebook_location_KNN 流程分析: 1.数据集获取(大型数据怎么获取? 放在电脑哪里? 算力怎么搞?) 2.基本数据处理(数据选取-确定特征值和目标值-分割数据集) 缩小数据范围 选择时间特征 去掉签到较少的地方 确定特征值和目标值 分割数据集 3.特征工程(特征预处理:标…...

【str_replace替换导致的绕过】
双写绕过 随便输入一个 usernameadmin&passwords 没有回显测试注入点 usernameadmin or 11%23&passwords 回显hello admin测试列数 usernameadmin order by 3%23&passwords测试回显位 usernameadmi union select 1,2,3%23&passwords 没有显示数据,推…...

如何用AI大模型提升挖洞速度
工具背景 越权漏洞在黑盒测试、SRC挖掘中几乎是必测的一项,但手工逐个测试越权漏洞往往会耗费大量时间,而自动化工具又存在大量误报, 基于此产生了AutorizePro, 那它是怎么提升效率一起来看看 AutorizePro 是一款专注于越权检测的 Burp 插件…...
两个数列问题
# 问题描述 给定长度分别为 n 和 m 的两个数列a[n]、b[m],和一个整数k。求|(a[i] - b[j])^2 - k^2|的最小值。 ## 输入格式 第一行有 2 个整数 n、m、k,分别表示数列 a、b 的长度,以及公式中的整数 k。 第二行有 n 个整数,表示…...

python中堆的用法
Python 堆(Headp) Python中堆是一种基于二叉树存储的数据结构。 主要应用场景: 对一个序列数据的操作基于排序的操作场景,例如序列数据基于最大值最小值进行的操作。 堆的数据结构: Python 中堆是一颗平衡二叉树&am…...

轮班管理新策略,提高效率与降低员工抱怨
良好轮班管理对企业关键,需提前计划、明确期望、保持灵活公平、加强沟通并利用轮班调度系统。ZohoPeople作为智能排班系统,提供轻松创建班次、自动更换、分配管理员、设置津贴及即时通知等功能,助力企业高效管理。 一、HR轮班管理的5大技巧 …...

spring-cloud-alibaba-nacos-config2023.0.1.*启动打印配置文件内容
**背景:**在开发测试过程中如果可以打印出配置文件的内容,方便确认配置是否准确;那么如何才可以打印出来呢; spring-cloud-alibaba-nacos-config 调整日志级别 logging:level:com.alibaba.cloud.nacos.configdata.NacosConfigD…...

数据结构:二叉树、堆
目录 一.树的概念 二、二叉树 1.二叉树的概念 2.特殊类型的二叉树 3.二叉树的性质 4.二叉树存储的结构 三、堆 1.堆的概念 2.堆的实现 Heap.h Heap.c 一.树的概念 注意,树的同一层中不能有关联,否侧就不是树了,就变成图了ÿ…...

hi3798mv100 linux 移植
# Linux开发环境搭建 ## uboot编译 1. 必须先安装gcc,要不然make 等命令无法使用 2. 配置arm 交叉编译链 # gcc sudo apt-get install gcc-9 gcc -v# 安装 Linaro gcc-arm-linux-gnueabihf,注意不是arm-linux-gnueabihf-gcc sudo apt-get install ar…...

Docker-Harbor概述及构建
文章目录 一、Docker Harbor概述1.Harbor的特性2.Harbor的构成 二、搭建本地私有仓库三、部署 Docker-Harbor 服务四、在其他客户端上传镜像五、维护管理Harbor 一、Docker Harbor概述 Harbor 是 VMware 公司开源的企业级 Docker Registry 项目,其目标是帮助用户迅…...
部署项目最新教程
3.3安装mysql 运行代码: yum install mysql 运行代码: yum install mysql-server 中间还是一样要输入y然后回车 运行代码: yum install mysql-devel 好,经过上面三步,mysql安装成功,现在启动mysql…...
linux证明变量扩展在路径名扩展之前执行
题目:怎么设计一组命令来证明变量扩展在路径名扩展之前执行。 为了证明变量扩展在路径名扩展之前执行,可以通过编写一个简单的 shell 脚本来观察这两个过程的顺序。我们可以使用以下步骤进行设计: 步骤 1:准备环境 在你选择的 …...

CentOS 7.9安装MySQL
下载Linux版MySQL安装包 下载地址https://downloads.mysql.com/archives/community/ 下载解压后 安装,按照从上至下顺序,一条一条执行即可安装完毕。 进入到rpm所在目录rpm -ivh mysql-community-common-8.0.26-1.el7.x86_64.rpm rpm -ivh mysql-comm…...

MacOS虚拟机安装Windows停滞在“让我们为你连接到网络”,如何解决?
1. 问题描述 MacOS在虚拟机安装win11过程中,停止在“让我们为你连接到网络”步骤,页面没有任何可以点击的按钮,进行下一步操作。 2. 解决方案(亲测有效) 到达该界面,按下ShiftF10(Windows&…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
Java 二维码
Java 二维码 **技术:**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...

七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...