函数栈帧的创建和销毁

目录

1.关于寄存器你应该知道的

2.函数栈帧的初步理解

3.简单的了解反汇编

4.函数调用的传参过程

5.函数返回值到底是如何返回的

6.总结回答开始的问题


1.局部变量是怎么创建的?

2.为什么局部变量不进行初始化就会是随机值?

3.函数如何传参和传参的顺序?

4.形参和实参到底是怎样的关系?

5.函数如何进行调用的?

6.函数调用结束之后是如何返回的?

相信在学习的过程中,你对上面的问题或多或少都会有些困惑,今天的博客--函数栈帧的创建和销毁就可以帮助你解决这些困惑;

这些都是和函数的栈帧的创建和销毁有关,这个函数栈帧在不同版本的编译器有关,略有差异但是大致相同,我们使用的编译器版本越高,越不容易观察找这个函数栈帧的过程,我们这里使用的是vs2013为例:

1.关于寄存器你应该知道的

我们之前学习的时候了解过,这个寄存器有eax,ebx,ecx,edx等等,我们在学习函数栈帧的时候,经常使用的两个寄存器就是ebp和esp,这两个寄存器存放的是地址,存放的这两个地址用来维护函数的栈帧;

2.函数栈帧的初步理解

每一个函数的调用,都要在栈区开辟空间,在栈区里面,我们会优先使用高地址,再使用低地址;我们的main函数开始执行之后,就会开辟main函数的函数栈帧,ebp  esp分别指向的就是main函数的函数栈帧的边界(如图所示);我们可以把这个函数栈帧创建的过程理解为一个盖房子的过程,我们就会从低向高处盖房子,我们的ebp指针也被称为栈底指针,esp也被称为栈顶指针,我们现在维护的是main函数的函数栈帧,当调用其他的函数的时候,这两个指针就会维护其他的函数的栈帧空间;

我们还需要了解的就是main函数其实也是被其他的函数调用的,那么main函数是被_tmainCRTStartup函数调用的,_tmainCRTStartup函数也是被mainCRTSartup函数调用的,我们了解就可以了,这样就可以让我们对于main函数的理解提高一个等级,而不是简简单单的只是一个主函数的概念;

3.简单的了解反汇编

上面展示的就是一些基本的反汇编代码,我们同样需要了解一些的,这样才方便我们对与函数栈帧创建和销毁的过程的理解;

我们对于栈这个空间,我们需要了解的预备知识就是压栈和出栈这两个专业术语:

push压栈:就是在栈的顶部放上数据;

pop出栈:就是从栈的顶部删除数据;

(1)上面的反汇编语言的第一句就是push   ebp这句代码的意思就是把ebp进行压栈的处理,像下面的图片展示的那样,我们的ebp就放到了这个栈的顶部

(2)第二句move肯定就是移动的意思呗,那么移动什么,如何进行移动呢?我们这里是把esp的值给ebp,我们的ebp原来是指向栈的底部的(不是下面的图片,下面的图片展示的是最后的结果),就是我们的ebp不是压栈了吗,每次进行压栈,我们的esp都要进行移动,因为esp是栈顶指针,那么把esp赋值给ebp就相当于是ebp指向esp的位置,也就是说我们的ebp指向了新的栈底

(3)第三行的esp减去oE4h这个就是在给main函数创建栈帧,我们esp减去一个值就是向上移动,移动到一个新的位置,移动的距离就是0E4h,这个时候ebp和esp各自指向了新的栈底和新的栈顶,我们这个时候就完成了main函数栈帧的创建;

(4)接下来反汇编里面是3个push,这三个都是进行压栈的操作,压栈完成之后的栈帧情况如下图所示(我们不需要了解这3个东西是什么,只需要了解这个过程就可以了),显然,这个过程里面每次进行压栈,esp都是同步进行移动的;

(5)接下来进行的是下面的这四行代码,

lea的全称是load effective address翻译之后就是下载有效地址空间;

dword是double word翻译过来就是双字,一个word是2个字节,double就是两倍的意思,double word就是4个字节;

两个move是在进行一个什么样的操作呢?39h代表的是次数,0cccccccc表示的是复制内容,经历过38h次之后,我们的main函数的栈帧里面放置的全部都是ccccccccccc这些内容(像图片里面展示的那样)

(6)接下来到了变量的创建,我们在图里也已经标注了出来,我们创建的三个变量abc分别初始化的数值就是10,20,0,图片里面就已经进行了初始化;

这里我们就已经可以解释一个上面的问题了,为什么我们强调好的代码风格是一定要进行变量的初始化的,因为我们初始化之后,我们初始化的数值就覆盖掉了原来的cccccccccccccc,但是如果我们不进行初始化而是直接进行打印,打印的结果像“烫烫烫烫烫烫烫烫”这种,相信在初学的时候你也一定遇到过这种情况,这些“烫烫烫烫”的实质就是ccccccccccccccccccc,如果我们的进行变量的初始化工作,就不会打印产生这样的随机值;

4.函数调用的传参过程

(1)我们走到add函数这一步之后,执行的操作是mov+push+mov+push这几步骤就是把bep-14h这个位置的值(就是我们的b)传递给eax,然后push这个eax,就是进行压栈,相当于把b=20这个数据压栈到了栈的顶部,下面的两行相信你也已经猜到了,就是把我们的a=10压到栈的顶部,压栈完成之后的情况如下图所示:

(2)接下来我们的就要进入这个call指令,call指令的作用就是记住call指令的下一行指令的地址00C21450为什么要记住这个地址呢,因为从下一步开始,我们就要进入add函数的内部了,经历了一系列的操作之后,我们就要回来吧,回到那一步指令呢?肯定是回到call指令的下一步指令,我们记住这个指令的地址,就可以调用结束的时候找到我们要执行的指令00C21450;

(3)接下来就进入了add函数的里面,里面的指令如下图所示:这个里面我们可以发现这些指令和我们的main函数当时的指令是一样的,就是进行add函数栈帧的创建;虽然过程是一样的,我们还是进行回忆强化一下:还是进行ebp和esp的移动,指向栈顶和栈底,把ebx   esi  edi进行压栈(这个过程和main函数栈帧的创建的过程是一样的),然后就是进行初始化,全部初始化为cccccccccccccccccc;

(4)接下来通过指令我们可以看到z这个变量被创建,初始化为0,但是我们的z=x+y,但是这个过程中x和y好像并没有出现,这个时候我们上一步压栈的a和b就要发挥作用了;

我们通过下面的指令也可以知道,就是z=x+y那一行里面,实际上这个ebp+8找到的就是我们当时压栈的a=10;ebp+0Ch实际上找到的就是b=20这个值,我们的ebp+8就是10放到eax里面,这个时候的eax就是10,然后我们的ebp+0Ch就是b=20放到eax里面,eax里面原来就有10这个时候把20放进去,就可以得到我们想加之后的结果30这个实际上就已经进行了我们的相加的操作,实现了这个函数的功能;

(5)下面展示的就是add函数栈帧创建之后的情况,这个里面是已经被全部初始化为cccccccc了

5.函数返回值到底是如何返回的

(1)return z指令的后面的ebp-8实际上就是我们的计算结果30,我们把这个数据存到eax这个寄存器里面,这个寄存器是不会随着add函数栈帧的销毁而消失的;

(2)返回之后的指令是,pop就是我们前面已经铺垫的出栈的操作,edi  esi  ebx全部出栈,这个时候我们的add函数的栈帧就应该被回收掉了,下面的一句指令指令就可以回收掉这个空间;

esp,ebp的意思就是把ebp的值给esp相当于是esp直接来到了栈底,这样就可以把这个add函数的空间回收掉了;

下面的就是add栈帧回收之后压栈情况:

(3)接下来我们的栈顶ebp是main函数的,这个时候ebp就找到main函数的栈底,这个时候的栈顶就变成了00C21450,这个指令还记得是什么吗,就是我们调用函数之前记住的指令,方便我们对调用完之后找到下一个指令,这个时候就发挥了作用;

(4)接下来的指令esp,8代表的是esp加上8,就相当于是放掉了ecx=10,eax=20这两个东西;

ebp-20其实就是我们的main函数里面的c这个变量,eax刚刚存放的是我们的z的30,这个时候把eax里面的30给了c就相当于是实现了参数的传递,接下来就是main函数空间的回收(同add函数相似);

6.总结回答开始的问题

(1)我们了解到了局部变量时怎么创建的:就是覆盖掉了原来的cccccccccc,我们的形参是压栈的,而且x和y是在main函数的栈帧里面,add使用的时候是到main函数栈帧里面找到这两个值,计算完之后把这个值存到eax里面,最后这个eax再赋值给main函数里面的c;

(2)我们的函数调用的时候,形参xy是压栈的,和我们的实际的xy并不在一个地方,因为xy是在栈帧里面创建之后压栈上去的,所以形参是实参的临时拷贝,形参的修改不会影响实参(因为我们的add函数使用的是压栈的xy并不是我们最开始创建的xy);

(3)我们的函数调用完成之后,会找到栈顶存放的call指令的下一条指令,进行后续的过程,这个地址的存储使用,返回值整个过程都是十分严谨的,既可以调用函数,调用完之后还是可以回到原来指令的下一步的。

  • 52
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值