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

函数栈帧的创建和销毁——“C”

各位CSDN的uu们你们好呀,今天小雅兰来为大家介绍一个知识点——函数栈帧的创建和销毁。其实这个知识点,我们很早之前就要讲,但是因为我的一系列原因,才一直拖到了现在,那么,话不多说,让我们一起进入函数栈帧的世界吧

我们学习了前面这么多内容,不由得会想起几个问题:

  • 局部变量是如何创建的?
  • 为什么局部变量不初始化内容是随机的?
  • 函数调用时参数是如何传递的?传参的顺序是怎样的?
  • 函数调用是怎么做的?
  • 函数的形参和实参分别是怎样实例化的?
  • 形参和实参的关系是什么?
  • 函数的返回值是如何带回的? 

带着这一肚子的疑惑,就有了今天的函数栈帧的创建和销毁了。


寄存器

什么是函数栈帧

什么是栈

解析函数栈帧的创建和销毁


首先,我还得给大家拓展一个知识点——寄存器

寄存器的功能是存储二进制代码,它是由具有存储功能的触发器组合起来构成的。一个触发器可以存储1位二进制代码,故存放n位二进制代码的寄存器,需用n个触发器来构成

按照功能的不同,可将寄存器分为基本寄存器移位寄存器两大类。

基本寄存器只能并行送入数据,也只能并行输出。

移位寄存器中的数据可以在移位脉冲作用下依次逐位右移或左移,数据既可以并行输入、并行输出,也可以串行输入、串行输出,还可以并行输入、串行输出,或串行输入、并行输出,十分灵活,用途也很广。

这边介绍一下寄存器的基本含义、基本概念、结构、工作原理、类型、存放代码满足条件、寄存器组织、寄存器寻址

 

 

 相关寄存器和汇编指令

 相关寄存器

 

  •  eax:通用寄存器,保留临时数据,常用于返回值
  •  ebx:通用寄存器,保留临时数据
  •  ebp:栈底寄存器
  •  esp:栈顶寄存器
  •  eip:指令寄存器,保存当前指令的下一条指令的地址

 

相关汇编命令

  •  mov:数据转移指令
  •  push:数据入栈,同时esp栈顶寄存器也要发生改变
  •  pop:数据弹出至指定位置,同时esp栈顶寄存器也要发生改变
  •  sub:减法命令
  •  add:加法命令
  •  call:函数调用,
  •       1.压入返回地址
  •       2.转入目标函数
  •  jump:通过修改eip,转入目标函数,进行调用
  •  ret:恢复返回地址,压入eip,类似pop eip命令

 什么是函数栈帧

我们在写C语言代码的时候,经常会把一个独立的功能抽象为函数,所以C程序是以函数为基本单位的。

那函数是如何调用的?

函数的返回值又是如何带会的?

函数参数是如何传递的?

这些问题都和函数栈帧有关系。

函数栈帧(stack frame)就是函数调用过程中在程序的调用栈(call stack)所开辟的空间,这些空间是用来存放:

   

    函数参数和函数返回值

   

    临时变量(包括函数的非静态的局部变量以及编译器自动生产的其他临时变量)

   

    保存上下文信息(包括在函数调用前后需要保持不变的寄存器)。

看到这里,我们就必须还想到一个问题——什么是栈? 


什么是栈

栈(stack)是现代计算机程序里最为重要的概念之一,几乎每一个程序都使用了栈,没有栈就没有函数,没有局部变量,也就没有我们如今看到的所有的计算机语言。

在经典的计算机科学中,栈被定义为一种特殊的容器,用户可以将数据压入栈中(入栈,push),也可以将已经压入栈中的数据弹出(出栈,pop),但是栈这个容器必须遵守一条规则:先入栈的数据后出栈(First In Last Out, FIFO)。就像叠成一叠的术,先叠上去的书在最下面,因此要最后才能取出。

在计算机系统中,栈则是一个具有以上属性的动态内存区域。程序可以将数据压入栈中,也可以将数据从栈顶弹出。压栈操作使得栈增大,而弹出操作使得栈减小。

在经典的操作系统中,栈总是向下增长(由高地址向低地址)的。 在我们常见的i386或者x86-64下,栈顶由成为 esp 的寄存器进行定位的。

在了解了这些准备工作之后,我们就可以进入我们的正题啦——解析函数栈帧的创建和销毁 


解析函数栈帧的创建和销毁

首先我们达成一些预备知识才能有效的帮助我们理解,函数栈帧的创建和销毁。

 1. 每一次函数调用,都要为本次函数调用开辟空间,就是函数栈帧的空间。

 2. 这块空间的维护是使用了2个寄存器: esp 和 ebp ebp 记录的是栈底的地址, esp 记录的是栈顶的地址。

 3. 函数栈帧的创建和销毁过程,在不同的编译器上实现的方法大同小异,本次演示以VS2010为例。  

函数的调用堆栈

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

 我们可以看到,main函数也确实被调用了

在VS2010中,main函数也是被其他函数调用的   __tmainCRTStartup  这个函数又是被调用的  mainCRTStartup

即,mainCRTStartup调用了__tmainCRTStartup,__tmainCRTStartup又调用了main函数

 

 现在转到我们的反汇编,把这个显示符号名的勾勾去掉,这样方便观察

 

 

 压栈(push)操作

 mov操作,表示把esp的值给ebp

 sub操作,表示esp的值减去0E4h

0E4h是一个十六进制数字,转为十进制为228

经过sub操作,esp的值就变了

 

 然后,esp就指向上面开辟的某一块空间了

 

 这一块空间,就是为我们的main函数预开辟的一块空间了,也就是main函数的栈帧

然后再是三个push操作,push了ebx、esi、edi

 

 

 再是lea操作,lea表示Load Effective Address,是为加载有效地址

 把[ebp+FFFFFF1Ch]的值加载到edi中,但是这个值不好观察,那我们还得把我们之前取消的显示符号名给勾上

 

 

这三个操作的意思是,把刚刚main函数的栈帧全部初始化为CCCCCCCC

dword的意思是double word(双字),一个字是两个字节,双字就是四个字节

 

 走了这么半天,竟然还没有执行一行有效的代码!!!

#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 ret = 0;ret = Add(a, b);printf("%d\n", ret);return 0;
}

 

 这就是我们的变量为什么要初始化的原因,如果不初始化的话,内存里面放的是一个随机值

 

接下来,就是调用函数

 

 

这几个动作就是在传参

 

 我们会发现,这个Add函数的指令和我们的main函数开始的指令几乎是一样的,这实际上就是在准备栈帧

 

 

其实初始化并不止这么多次,把33h这个十六进制数字换成十进制,是多少次就初始化多少次CCCCCCCC

 

 

通过画图,我们可以清楚地知道,并没有给形参创建空间,这也验证了我们之前的结论:实参传递给形参的时候,形参是实参的一份临时拷贝,改变形参是不会影响实参的

把[ebp-8]的值放到eax这个寄存器中

 

 

 

 


好啦,小雅兰今天的函数栈帧的创建和销毁的内容就到这里了,总体来说,我觉得这个内容比较地抽象,难度也是很大的,对于我们这种初学者来说,但是,不奢求一遍就把它看懂,但求每多看一遍,收获的知识点就多一点点,这样我就心满意足啦!!!

 

http://www.lryc.cn/news/1911.html

相关文章:

  • 腾讯云对象存储+企业网盘 打通数据链“最后一公里
  • 在浏览器输入url到发起http请求,这过程发生了什么
  • PyTorch学习笔记:nn.ReLU——ReLU激活函数
  • 同步线程
  • 服务端返回内容跨域CORS之后,也在chrome/edge浏览器里显示出响应信息
  • DHCP中继及配置
  • 中国社科院与美国杜兰大学金融管理硕士,让我们相遇在春暖花开时
  • MySQL---单表查询、多表查询
  • 3年自动化测试这水平?我还不如去招应届生
  • 5 个自定义 React Hooks 将改变你的代码
  • Java学习笔记-03(API阶段)
  • Django自定义模板标签的使用详解
  • 洗地机怎么选?洗地机品牌排行榜
  • CSS的元素显示模式
  • 【MySQL Shell】8.9.1 在 InnoDB ClusterSet 中隔离集群
  • Ubuntu20.04+cuda11.2+cudnn8.1+Anaconda3安装tensorflow-GPU环境,亲测可用
  • 剑指Offer 第27天 JZ75 字符流中第一个不重复的字符
  • 科研试剂供应1476737-97-9,Bis-PEG2-endo-BCN可发生点击反应
  • Zabbix 构建监控告警平台(一)--部署安装
  • 【nodejs】nodejs入门核心知识(命令行使用、内置模块、node 模块化开发)
  • 5. Spring 事务
  • 【堆】数据结构堆的实现(万字详解)
  • Docker进阶 - 9. docker network 之自定义网络
  • springcloud-工程创建(IDEA)
  • Blender——物体的随机分布
  • 一文教你玩转 Apache Doris 分区分桶新功能
  • Spring JdbcTemplate 和 事务
  • C/C++:程序环境和预处理/宏
  • 什么是死锁?死锁产生的四个必要条件是啥?如何避免和预防死锁的产生?
  • 工程管理系统源码-物料管理-工程项目管理系统-建筑施工管理软件