目录


传统艺能????

小编是大一菜鸟不赘述,欢迎大佬指点江山

此前博客​​点我!点我!请搜索博主 【知晓天空之蓝】​​点我!点我!请搜索博主 【知晓天空之蓝】或扫码进入!

乔乔的gitee代码库(打灰人 )​​欢迎访问,点我!​


(https://blog.51cto.com)感谢支持!

过渡区????

现在是北京时间10:38,平平淡淡的一天,刚上完高数回来,数学课混了条狗子进来,在旁边过道蹲着再现忠犬八公,事实证明,狗都在学高数,我还不能寄。

栈帧详解——C语言进阶_c语言

正片开始????

今天来讲讲我对栈帧创建与销毁的拙见。

理解什么是栈帧首先知道什么是栈:


在数据结构中, 栈是限定仅在表尾进行插入或删除操作的线性表。栈是一种数据结构,它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据。


栈有什么用?


在计算机系统中,栈也可以称之为栈内存是一个具有动态内存区域,存储函数内部(包括main函数)的局部变量和方法调用和函数参数值,是由系统自动分配的,一般速度较快;存储地址是连续且存在有限栈容量,会出现溢出现象程序可以将数据压入栈中,也可以将数据从栈顶弹出。压栈操作使得栈增大,而弹出操作使栈减小。
栈用于维护函数调用的上下文,离开了栈函数调用就没法实现。


讲到这里,小朋友你是否有很多问号?那打住,我们抛开无聊的学术前文,另起炉灶。

寄存器????

要讲清楚栈帧就必须理解一手寄存器。尤其是 ebp,esp这2个寄存器中存放的地址,这两个地址是用来维护函数栈帧的。

栈帧详解——C语言进阶_堆栈_02

寄存器有很多种这里不赘述

栈帧详解——C语言进阶_栈_03

main函数创建????

我们这里随便搞一个最简单的Add函数

int   add(int x,int y)
{
int z;
z=x+y;

return z;
}

int main()
{
int data1;
int data2;
int ret;

while(1)
{
int data1,data2 = 0;
scanf("%d %d",&data1,&data2);
add(data1,data2);
return 0;
}

搞栈帧的话我的编译器是不适合的,我是vs2019,因为编译器越高级函数的封装越复杂周密,不容易我们去剖析栈帧,我就尽量语言表达严谨一点吧。编译器反汇编过程就能反应我们栈帧创建的过程,这是我在网上找的反汇编页面可以参考一下

栈帧详解——C语言进阶_堆栈_04

其中反汇编用到的指针我们要清楚意义:

栈帧详解——C语言进阶_指针_05

在编译器中,main函数也是会被其他函数调用的,调用堆栈窗口后反汇编可以看到如下字样:

main
_tmainCRTStartup
mainCRTStartup

后面两句意义不明的玩意儿就是在调用main函数。为什么要讲这个呢?我们说每一次函数调用都要分配空间,main函数不例外也要分配栈帧空间。

以下内容和上面汇编指令表食用更佳:

首先 push ,即压栈,就是往栈sei东西进去。push 会让esp让低地址走,就会在原先基础上压进来一个 ebp 指针。

栈帧详解——C语言进阶_指针_06

接下是 mov 指针,mov把后面的指针赋到前面去,esp给了ebp,也就是相当于在移位。

栈帧详解——C语言进阶_栈_07

接下来是 sub 减法操作,减去一个内容来使esp指针走向低地址来开辟main函数栈帧。

栈帧详解——C语言进阶_栈_08

过程模拟如下:

栈帧详解——C语言进阶_开发语言_09

局部变量创建????

接下来esp已经走到那几个内容的头上去了,这时出现了 lea 指针,即 load effective address 加载有效地址,其实在这个指针指定对象里面放入一个地址

栈帧详解——C语言进阶_c语言_10

我们后面的 [ebp-0C0h],其实就是刚刚 sub操作,本质上还是原来开辟栈帧起点 ebp 的地址,把这个地址放入edi 里面。

栈帧详解——C语言进阶_堆栈_11

接下来的连续 mov 时在把从edi 开始的 30h 这么多个空间里面的 dword(double word-四字节数据)全部初始化成 eax 里面 “0CCCCCCCCh”的内容,保证为main函数预开辟的内存全变成 “CCCCCCCCh”,这么说来改的还是蛮多的。

接下来当我们创建变量时,比如 int a = 10;就会出现类似下面字样:

int a = 10;                      
00C2142E C7 45 EC 0A 00 00 00 mov dword ptr [ebp-8h],0Ah

这里就是在创建局部变量了, ebp指针减了 8h,这个 8h 就是给a留的位子**(这里的 h 是编译器给的标识,我们只需要明白这是一个十六进制数)**就行了。所以总结一下,其实创建方式与main函数没有太大出入。

函数部分????

Add函数传参时也是在将 esp 进行压栈,但注意,这时的esp里面的值是 10,相当于是在传 10 这个值。传完参紧接着就会调用函数

00C2144B E8 91 FC FF FF           call             00C210E1

call 指针作用就是调用函数,F11 执行call指令后会发现在跳到作用域的同时,他会把 call指令的下一条指令的地址传到里面,从顶上压进来一个main函数的 ebp ,这时 esp 会继续往上面跑,一但函数执行完后返回值就会很自然的回到该地址。

栈帧详解——C语言进阶_堆栈_12

在main函数的 ebp 上面又会传统艺能,以相同的方式开辟 Add 函数的空间,又初始化成全 c,以相同方式创建临时变量……

这时你可能会注意到传进函数的 x,y去哪里了?其实已经为他准备好了,在返回进行下一项指令时,x,y就会乖乖跑到这片空间储存

栈帧详解——C语言进阶_开发语言_13

Add函数完成后回把传的参返回, 就是我们的 pop 指针,即出栈,这里参数每从栈顶pop一次 esp 指针就会上移一个单位,ebp也会随之退回一个单位,利用指针的偏移量找回他的形参,最后返回值ret,其逻辑本质上就是弹出main ebq那里的下一项指令的地址

我们走出函数后,esp,ebq会回收,这时这块空间就会直接销毁,挫骨扬灰。

这样整个函数部分就完美的呈现出来了。

形参与实参????

形参确实是我在压栈时开辟的空间,这个空间他是独立的,只有值是相同的,形参本质上是实参的一份临时拷贝,改变形参并不影响实参,那返回值是怎么带回来的呢?答:是通过寄存器。

今天先到这里,摸了家人们。