C语言进阶栈帧示例详解教程

正片开始

今天来讲讲我对栈帧创建与销毁的拙见。
理解什么是栈帧首先知道什么是栈:

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

栈有什么用?

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

栈用于维护函数调用的上下文,离开了栈函数调用就没法实现。

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

寄存器

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

C语言进阶栈帧示例详解教程_第1张图片

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

C语言进阶栈帧示例详解教程_第2张图片

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语言进阶栈帧示例详解教程_第3张图片

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

C语言进阶栈帧示例详解教程_第4张图片

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

main
_tmainCRTStartup
mainCRTStartup

后面两句意义不明的玩意儿就是在调用main函数。为什么要讲这个呢?我们说每一次函数调用都要分配空间,main函数不例外也要分配栈帧空间。
以下内容和上面汇编指令表食用更佳:
首先 push ,即压栈,就是往栈sei东西进去。push 会让esp让低地址走,就会在原先基础上压进来一个 ebp 指针。

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

过程模拟如下:

C语言进阶栈帧示例详解教程_第5张图片

局部变量创建

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

在这里插入图片描述


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

在这里插入图片描述

接下来的连续 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语言进阶栈帧示例详解教程_第6张图片

在main函数的 ebp 上面又会传统艺能,以相同的方式开辟 Add 函数的空间,又初始化成全 c,以相同方式创建临时变量……
这时你可能会注意到传进函数的 x,y去哪里了?其实已经为他准备好了,在返回进行下一项指令时,x,y就会乖乖跑到这片空间储存

在这里插入图片描述

Add函数完成后回把传的参返回, 就是我们的 pop 指针,即出栈,这里参数每从栈顶pop一次 esp 指针就会上移一个单位,ebp也会随之退回一个单位,利用指针的偏移量找回他的形参,最后返回值ret,其逻辑本质上就是弹出main ebq那里的下一项指令的地址。
我们走出函数后,esp,ebq会回收,这时这块空间就会直接销毁,挫骨扬灰。
整个函数部分就完美的呈现出来了。

形参与实参

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

以上就是C语言进阶栈帧示例详解教程的详细内容,更多关于C语言栈帧的资料请关注脚本之家其它相关文章!

你可能感兴趣的:(C语言进阶栈帧示例详解教程)