C语言杂谈:函数栈帧,函数调用时到底发生了什么
我们都知道在调用函数时,要为函数在栈上开辟空间,函数后续内容都会在栈帧空间中保存,如非静态局部变量,返回值等。这段空间就叫栈帧。
当函数调用,就会开辟栈帧空间,函数返回时,栈帧空间就会被释放。这里的释放并非清空,而是让其无效化,可以后续的使用。
1,用到的寄存器和汇编指令
1,相关寄存器
eax:保存临时数据,返回值
ebx:保存临时数据
ebp:栈底寄存器
esp:栈顶寄存器
eip:指令寄存器,保存当前指令的下一条指令的地址
2,相关汇编指令
call:函数调用
mov:数据转移
push:出栈指令
pop:入栈指令
sub:减法指令
add:加法指令
jump:修改eip,转入目标函数调用
ret:恢复返回地址
2,虚拟内存地址
除了这些指令和寄存器,我们也需要了解虚拟内存地址是什么样子的
简单来说,高地址向低地址存储的内容分别是:内核,栈(向下增长),共享库的内存映射区域,
堆(向上增长),数据区(未初始化的数据,已初始化的数据),代码区,以及不分配使用的部分
区域。
值得注意的时,栈区是向下增长的,堆区是向上增长的。
3,代码预览
简单总结调用函数时,发生的行为:
1,先形参实例化,按照参数列表从右向左
2,保护现场//将函数返回地址压入栈,转入目标函数
3,执行函数体
4,释放局部变量的栈帧空间
5,恢复现场//获得函数返回地址,释放栈帧空间
6,继续后续主函数语句
下面是演示所用的c语言代码
#include<stdio.h>
int add(int a,int b)
{return a+b;
}
int main()
{int a=10;int b=10;int c=0;c=add(a,b);return 0;
}
4,调用过程
使用vs2022,点击调试,打开反汇编,打开寄存器
A:主函数栈帧建立
首先我们要知道,主函数也是函数,也需要建立函数栈帧,它被_tmainCRTStartup函数调用,而_tmainCRTStartup又被mainCRTStartup函数调用,mainCRTStartup函数又是被操作系统所调用的。
00007FF7AD0618D0 push rbp
00007FF7AD0618D2 push rdi
00007FF7AD0618D3 sub rsp,148h
00007FF7AD0618DA lea rbp,[rsp+20h]
00007FF7AD0618DF lea rcx,[__B782E998_栈帧@c (07FF7AD071008h)]
00007FF7AD0618E6 call __CheckForDebuggerJustMyCode (07FF7AD061370h)
这些是主函数栈帧建立的汇编代码
我们暂且不管这些代码,去关注寄存器的变化,重点关注esp和ebp寄存器
这是主函数栈帧建立前栈底寄存器和栈顶寄存器的位置
这是主函数栈帧建立后栈底寄存器和栈顶寄存器的位置
可以看到栈顶寄存器的数值减少了(D20-C2C)个字节,这就说明了我们栈是由高地址向低地址增长
的,具体的建立过程我们在add函数时介绍。
B:变量初始化
单击F10我们观察寄存器
EIP指向下一条要指向的指令地址,值为00C71985,正是int a=10;这条指令。
此时,栈内空间应该是这样的:
点击F10,将变量a入栈,因为栈是从低地址到高地址增加的,所以我们将内存监视器调到010FF778,观察前后变化。
点击前
点击后
我们可以发现,变量a成功入栈了,距离栈底寄存器所在位置向上偏移八个字节单位
,同理我们将变量b和c入栈
观察到同样入栈成功,这里变量b也是0a的原因是因为数值相同,编译器进行了处理。
现在主函数栈帧就添加了三个变量。
接下来就是调用函数给c赋值了,一共有七条指令,我们一个个来看。
首先是调用Add()前(即call指令前)的4条指令,我们可以看出前两条指令的作用是先将变量b的值移
动到eax寄存器,然后以压栈push的方式压入栈中,栈顶寄存器更新,先下增长。
这里两个临时变量的产生,就是我们所说的形参实例化。我们需要注意两个点,一个是这是在调用
函数前生成的,其次就是压栈顺序是形参列表从右向左。
接下来,我们将执行函数调用指令,因为我们是通过跳转指令修改eip寄存器转入目标函数地址,
Add函数调用结束后还需返回main函数执行后续代码,所以我们需要将下一条指令的地址先保存起
来,然后进行跳转。
因此这个指令分为两步:1.将返回地址压入栈中 2.转入目标函数。
点击F11进入函数,我们可以发现函数返回后的指令地址被压入栈中(010FF67C ),然后修改eip进行跳转,转入add()函数:
C:转入add函数
下面三条是栈帧建立过程
首先是第一条指令,单击F10,将栈底寄存器的内容压入栈中,即把main函数栈底的地址压入栈中:
因为是压栈,所以栈顶寄存器向上偏移四个字节。
然后是第二条指令,单击F10,将栈顶寄存器的内容移动到栈底寄存器,使得栈顶寄存器和栈底寄存器指向同一个地址空间:
最后是第三条指令,单击F10,将esp栈顶寄存器的内容减去0CCH,使其向低地址偏移0C0h个字节,如下:
至此,add函数栈帧建立完成。
建立的栈帧空间
之后的内容我们在之前都有过类似的,我们需要知道几点
1,retnru语句计算时,函数参数是从之前的临时变量处取得数值进行计算
2,计算结果存储在eax寄存器中
至此,ADD函数调用完毕,进入最后一步,栈帧释放。
D:add函数销毁
栈帧的销毁我们重点来谈后三条语句,前几条语句对应着前面栈帧创建时的初始化操作,进行设置,我们不去管。
首先是第一条mov命令,我们单击F10运行,ebp栈底寄存器的值赋给esp栈顶寄存器,此时ebp与esp指向同一个地址空间:
在这时,理论来说我们就已经释放完成了,因为add函数的内存空间已经被覆盖了。
接下来就是恢复main函数栈帧的操作了。
我们单击F10,执行下一条pop指令,将栈顶内容弹出并放入ebp栈底寄存器中,还记得我们
刚才栈顶放的是什么了吗,是main函数栈底地址,所以此时ebp重新指向main函数栈底。
同时esp栈顶寄存器的指向发生改变。
之后执行ret指令,ret作用是恢复返回地址,压入eip,即把栈顶元素弹出到eip指令寄存器
中,改变下一条执行的指令。我们单击F10,发现返回到了main函数,此时eip的内容就是我们之
前保存的下一条main函数指令地址,esp栈顶寄存器发生改变:
之后执行main函数中的下一条add指令,将esp栈顶寄存器的值加8并存回esp栈顶寄存器,此时esp向下偏移8个字节,指向原main函数栈顶,释放临时变量的栈帧空间。
最后使用mov将值赋给c,打印,函数结束。
后面的printf函数也会建立栈帧,但类似,不再讨论。
相关文章:

C语言杂谈:函数栈帧,函数调用时到底发生了什么
我们都知道在调用函数时,要为函数在栈上开辟空间,函数后续内容都会在栈帧空间中保存,如非静态局部变量,返回值等。这段空间就叫栈帧。 当函数调用,就会开辟栈帧空间,函数返回时,栈帧空间就会被释…...

【Qt】win10,QTableWidget表头下无分隔线的问题
1. 现象 2. 原因 win10系统的UI样式默认是这样的。 3. 解决 - 方法1 //横向表头ui->table->horizontalHeader()->setStyleSheet("QHeaderView::section{""border-top:0px solid #E5E5E5;""border-left:0px solid #E5E5E5;""bord…...

前端 实现有时间限制的缓存
首先我们需要创建一个名为TimeLimitedCache的构造函数,然后定义一些方法,如set, get,和count。以下是具体的示例代码: // 定义 TimeLimitedCache 构造函数 var TimeLimitedCache function( ) {// 初始化一个空的 cache 对象,用于…...

前端将xlsx转成json
第一种方式,用js方式 1.1先安装插件 万事都离不开插件的支持首先要安装两个插件 1.2. 安装xlsx cnpm install xlsx --save注:这块我用的cnpm,原生的是npm,因为镜像的问题安装了cnpm,至于怎么装网上一搜一大堆 1.3安…...

使用LLaMA-Factory微调大模型
使用LLaMA-Factory微调大模型 github 地址 https://github.com/hiyouga/LLaMA-Factory 搭建环境 git clone --depth 1 https://github.com/hiyouga/LLaMA-Factory.git cd LLaMA-Factory在 LLaMA-Factory 路径下 创建虚拟环境 conda create -p ./venv python3.10激活环境 c…...

C语言二级指针、指针数组
一、二级指针 指针变量也是变量,是变量就应有地址,那指针变量的地址存放在哪里?存放在二级指针变量。 此时,*ppa pa,**ppa a。 二、指针数组 指针数组,顾名思义就是存放指针的数组。 数组每个元素为int类…...

python方法
目录 公共方法 1.容器类型之间的转化 2.运算符 3.通用函数 公共方法 1.容器类型之间的转化 # 类型转化 data_str itcast data_list [hadoop, spark, hive, python, hive] data_tupe (hadoop, spark, hive, python, hive) data_set {hadoop, spark, hive, python,…...

0基础学习区块链技术——去中心化
大纲 去验证的中心化验证者如何验证验证者为什么要去传播 去确认的中心化去存储的中心化 “去中心化”是区块链技术的核心。那么我们该如何理解这个概念呢? 我们可以假想在一次现实转账中,有哪些“中心化”的行为: 判断余额是否足够。即判断…...

索引的强大作用和是否创建的索引越多越好
在经常查询字段上创建索引。 在大数据的情况下,在索引上查找可以提交10倍以上甚至1000倍的速度。 实际测试,不在索引上查找用时12秒左右。建立索引,在索引上查找速度提高只耗时1.1秒左右。当然索引也是一把双刃剑,在一个表上创建索…...

批量GBK转UTF-8
大家都有这样的需求,把GBK编码的源代码转换成utf-8编码的源代码。 毕竟现在UTF-8的支持是很好的。 以前一些旧代码是GBK的,尤其是里面的注释,如果不采用UTF-8,在vscode里面可能看着就是乱码。 试了各种工具,最后发现…...

C#WPF数字大屏项目实战08--生产量/良品统计
1、区域划分 生产量/良品统计这部分位于第二列的第二行 2、livechart拆线图 定义折线图,如下: <lvc:CartesianChart> <lvc:CartesianChart.Series> <!--设置Series的类型为 Line 类型, 该类型提供了一些折线图的实现--> <lvc:LineSeries/>…...

22、matlab锯齿波、三角波、方波:rectpuls()函数/sawtooth()函数/square()函数
1、采样的非周期性矩形 语法 语法1:y rectpuls(t) 返回一个以数组 t 中指示的采样时间采样的连续非周期性单位高度矩形脉冲,该矩形脉冲以 t 0 为中心。 语法2:y rectpuls(t,w) 生成一个宽度为 w 的矩形 参数 t:采样时间 w:矩形宽度…...

手机和WINDOWS电脑蓝牙连接后怎样放歌,无法选择媒体音频 蓝牙媒体音频勾选不上
手机和电脑蓝牙连接后怎样放歌 要将手机通过蓝牙连接到电脑并播放音乐,可以按照以下步骤操作: 确保手机和电脑都支持蓝牙功能,并且蓝牙功能已经开启。 在电脑上,打开“设置”> “设备”> “蓝牙和其他设备”。 点击“添…...

MatrixOne→MatrixOS:矩阵起源的创业史即将用“AI Infra”和“AI Platform”书写新章程
在数字化浪潮的推动下,MatrixOne的故事就像一部科技界的创业史诗,它始于一个简单而宏伟的梦想——构建一个能够支撑起新一代数字世界的操作系统。想象一下,在AIGC时代,数据流动如同“血液”,算法运转如同“心跳”&…...

vue3将自定义组件插入指定dom
需求简要描述 页面渲染了一个 id 为 videoPlayerId 的div盒子,代码自定义了一个名为CustomComponent 的组件,现在需要在vue3中,通过纯 js 的方式将组件 CustomComponent 插入 videoPlayerId 的div中,作为其子节点。 实现代码 C…...

flutter封装日历选择器(单日选择)
简单封装: 引入库:table_calendar import package:generated/l10n.dart; import package:jade/utils/JadeColors.dart; import package:jade/utils/Utils.dart; import package:util/easy_loading_util.dart; import package:flutter/material.dart; im…...

SwiftUI调用相机拍照
在 SwiftUI 中实现拍照功能,需要结合 UIViewControllerRepresentable 和 UIImagePickerController 来实现相机功能。下面是一个详细的示例,展示如何使用 SwiftUI 来实现拍照功能: 1. 创建一个 ImagePicker 组件 首先,创建一个 U…...

elasticsearch (dsl)
正排索引 和 倒排索引 正排索引:通过id ,查询content 倒排索引:通过content,查询到符合的 ids eg: 正排索引就是通过《静夜思》,找到整片文章。 倒排索引通过“明月”,找到《静夜思》 《望…...

聊聊大模型微调训练全流程的思考
前言 参考现有的中文医疗模型:MedicalGPT、CareGPT等领域模型的训练流程,结合ChatGPT的训练流程,总结如下: 在预训练阶段,模型会从大量无标注文本数据集中学习领域/通用知识;其次使用{有监督微调}(SFT)优化…...

Python变量符号:深入探索与实用指南
Python变量符号:深入探索与实用指南 在Python编程的世界中,变量符号扮演着至关重要的角色。它们不仅是存储数据的容器,更是构建复杂逻辑和算法的基础。然而,对于初学者来说,Python的变量符号可能会带来一些困惑和挑战…...

实验八 页面置换模拟程序设计
网上找到的程序得到的答案经过手算验证是错的,所以自己实现了一个,具体实现看代码吧,多余的操作已经去掉了。 #include <stdio.h> #include <stdlib.h> #include <stdbool.h>#define VM_PAGE 7 /*假设每个页面可以存放10…...

Spring类加载机制揭秘:深度解析“卸载”阶段
1. 引言 在Spring框架中,类的加载和卸载是一个复杂但至关重要的过程。加载主要涉及将类的字节码加载到JVM中,创建对应的Class对象,并准备使其可用的过程。而卸载,则是指当一个类不再被需要时,将其从JVM中清除…...

Jupyter Notebook快速搭建
Jupyter Notebook why Jupyter Notebook Jupyter Notebook 是一个开源的 Web 应用程序,允许你创建和分享包含实时代码、方程、可视化和解释性文本的文档。其应用包括:数据清洗和转换、数值模拟、统计建模、数据可视化、机器学习等等。 Jupyter Notebo…...

Linux C语言:数组的定义和初始化
一、数组 1、定义 在程序设计中,为了处理方便,把具有相同类型的若干变量按有序的形式组织起来,具有一定顺序关系的若干个变量的集合就是数组 。 2、特点 组成数组的各个变量称为数组的元素数组中各元素的数据类型要求相同元素在内存中是连…...

spring框架限制接口是否要登录过才能访问
1、引入spring 、spring boot依赖,这部分不再多说,正常开发spring boot项目就可以。 2、定义类,实现WebMvcConfigurer接口 package com.hmblogs.config;import com.hmblogs.config.web.interceptor.PortalTokenInterceptor; import org.spri…...

【全开源】废品回收垃圾回收小程序APP公众号源码PHP版本
🌟废品回收小程序:绿色生活的新助手🌱 一、引言 随着环保意识的逐渐提高,废品回收成为了我们日常生活中的重要一环。但是,如何更方便、高效地进行废品回收呢?今天,我要向大家推荐一款超级实用…...

勒索软件分析_目标文件扫描行为分析
BlackBasta首先通过 FindFirstVolumeW 与 FindNextVolumeW 实现系统中第一个卷的搜索和其余卷的遍历,之后通过 GetVolumePathNamesForVolumeNameW 检索指定卷的驱动器号和挂载的文件夹路径列表,然后通过 GetVolumeInformationW 获取关于指定卷的信息,具体代码如下所示。 Fin…...

2024050401-重学 Java 设计模式《实战代理模式》
重学 Java 设计模式:实战代理模式「模拟mybatis-spring中定义DAO接口,使用代理类方式操作数据库原理实现场景」 一、前言 难以跨越的瓶颈期,把你拿捏滴死死的! 编程开发学习过程中遇到的瓶颈期,往往是由于看不到前进…...

HTML跨年烟花
目录 写在前面 关于小编 HTML简介 程序设计 系列文章 写在后面 写在前面 学会了这个html烟花秀,跨年就不缺文案喽~ 关于小编 平易近人,慈眉善目,爱交朋友,舍己为人,和蔼可亲,能说会道,…...

微服务第二轮
学习文档 背景 由于每个微服务都有不同的地址或端口,入口不同 请求不同数据时要访问不同的入口,需要维护多个入口地址,麻烦 前端无法调用nacos,无法实时更新服务列表 单体架构时我们只需要完成一次用户登录、身份校验ÿ…...