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

news/2024/11/27 4:53:49/

各位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.ppmy.cn/news/23965.html

相关文章

canal使用说明:MySQL、Redis实时数据同步

1. canal简介 canal是阿里开源的数据同步工具&#xff0c;基于bin log可以将数据库同步到其他各类数据库中&#xff0c;目标数据库支持mysql,postgresql,oracle,redis,MQ,ES等 canal分成服务端deployer和客户端adapter&#xff0c;我们可以部署多个&#xff0c;同时为了方便管…

状态机设计中的关键技术

⭐本专栏针对FPGA进行入门学习&#xff0c;从数电中常见的逻辑代数讲起&#xff0c;结合Verilog HDL语言学习与仿真&#xff0c;主要对组合逻辑电路与时序逻辑电路进行分析与设计&#xff0c;对状态机FSM进行剖析与建模。 &#x1f525;文章和代码已归档至【Github仓库&#xf…

算法第十五期——动态规划(DP)之各种背包问题

目录 0、背包问题分类 1、 0/1背包简化版 【代码】 2、0/ 1背包的方案数 【思路】 【做法】 【代码】 空间优化1&#xff1a;交替滚动 空间优化2&#xff1a;自我滚动 3、完全背包 【思路】 【代码】 4、分组背包 核心代码 5、多重背包 多重背包解题思路1:转化…

业务逻辑漏洞

现在的互联网网站&#xff0c;存在高危漏洞的很少&#xff0c;就算有&#xff0c;你也挖不到&#xff0c;所以重点还是在逻辑漏洞 定义 逻辑错误漏洞是指由于程序逻辑不严谨或逻辑太复杂&#xff0c;导致一些逻辑分支不能够正常处理或处理错误。通俗地讲:一个系统的功能太多后…

C++-类和对象(上)

类和对象&#xff08;上&#xff09;一&#xff0c;构造函数1&#xff0c;概念2&#xff0c;特性二&#xff0c;析构函数1&#xff0c;概念2&#xff0c;特性三&#xff0c;拷贝构造1&#xff0c;概念2&#xff0c;特性四&#xff0c;运算符重载1&#xff0c;概念2&#xff0c;…

6.14 Rayleigh商

定义 矩阵在某个向量处的瑞利商Rayleigh quotient是这样定义的: ρ(x):xHAxxHx\rho(x) :\frac{x^HAx}{x^Hx} ρ(x):xHxxHAx​   这个怎么理解呢?上面是埃尔米特内积的表达式&#xff0c;下面是标准埃尔米特内积。但是矩阵不一定是对称阵&#xff0c;如果不是复数的话&#x…

运动基元(二):贝塞尔曲线

贝塞尔曲线是我第一个深入接触并使用于路径规划的运动基元。N阶贝塞尔曲线具有很多优良的特性,例如端点性、N阶可导性、对称性、曲率连续性、凸包性、几何不变性、仿射不变性以及变差缩减性。本章主要介绍贝塞尔曲线用于运动基元时几个特别有用的特性。 一、贝塞尔曲线的定义 …

(1分钟突击面试) 高斯牛顿、LM、Dogleg后端优化算法

高斯牛顿法 LM法 DogLeg方法编辑切换为居中添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09;知识点&#xff1a;高斯牛顿是线搜索方法 LM方法是信赖域方法。编辑切换为居中添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09;这个就是JTJ是…