关于函数栈帧的创建与销毁

函数栈帧的创建和销毁

在我们的计算机里有一种东西叫寄存器,寄存器是独立于我们的内存的,寄存器有我们的这些类型eax、ebx、ecx、edx、ebp、esp等等。在这些寄存器中,esp和ebp这两个寄存器中存放的是地址,这两个地址是用来维护函数栈帧的,每一个函数被调用,都要在栈区创建一个空间。下面我们用一个例子来讲解函数栈帧的具体实现。

Int Add(int x,int y)

{

Int z = 0;

z = x + y;

return z;

}

Int main()

{

Int a = 10;

Int b = 20;

Int c = 0;

c = Add(a,b);

printf(“%d\n”,c);

return 0;

}

 

我们以上的代码大致呈现的一个函数栈帧图如上面所示,每调用一个函数就会在我们的栈区开辟一块新的空间,我们的main函数其实也是被调用的,被_tmainCRTStartup这个函数所调用,所以在main函数的底下还有一片区域留给了_tmainCRTStartup,说明是被他调用的(当然这个_tmainCRTStartup函数也是被我们的mianCRTStartup函数调用的)。这里说一句题外话,函数栈帧的开辟是从高地址到低地址的,并且就像堆砖头一样一层一层往上压,我们也把这种开辟函数栈帧的方式称为压栈,你调用什么函数,我们就帮你往顶上压这个函数的函数栈帧,函数栈帧说白了就是一片空间,给函数一个舞台,让它在这个舞台上能展示自己。ebp和esp所维护的函数栈帧是我们当前所正在调用的函数,比如说我上面这个图画出来的esp和ebp分别在main函数的栈顶和栈底,说明我们此刻正在调用的是main函数。这里提到维护函数栈帧的意思,我个人的鄙见就是指这个函数你现在放心可以使用,因为正在被我保护着,不会受到外界的影响。

下面我们来看整个函数栈帧的具体过程,我们这个地方为了方便大家的理解,就不去给大家放出汇编语言了,我们用图片来描述整个过程。

  1. 首先一开始调用_tmainCRTStartup这个函数,此时我们发现esp、ebp正在维护它。

 2.这个时候我们要开始调用main函数了,ebp上升到了esp的位置,此时他们两个都指向栈顶的这个位置。

3.esp往上升

 

4. 压栈,压三个寄存器上去,每压一个寄存器,我们顶部的esp就往上走一个

5.压一个esi 

6.最后压一个edi

 

 7.上述过程结束,意味着main函数的函数栈帧已经开辟完毕了,下面一步操作叫初始化,就是将edi寄存器以下的内存初始化,初始化的值为随机值在内存中表现为cc,所以说我们一定在创建变量以后要给变量初始化一下,不然变量的值就是一个随机值。

8.下面我们的流程就是将几个我们创建的变量依次放到我们的main函数里面,但是我们要注意就是我们整个的变量创建,在栈区里面并不是连续的,具体的情况是这样的,在VS2019下,我观察到a于b之间相差了12个字节,b于c之间也相差了有12个字节,所以说这些变量在main函数的栈帧中开辟的空间并不是连续的

9.在main函数中创建了变量了以后,下面就是去调我们的Add函数,我们传参的顺序是从右向左进行的,比如 Add( a , b )就是从我们的b开始调用。

10.像刚才一样,传参也是往顶上压栈的一个过程,我们先把变量b 的值放到寄存器eax中,然后将eax往上压栈,然后是将变量a的值放到寄存器ecx中,寄存器往eax上面压,同时每压一个栈,我们esp就会往上移一个单位

 

 

11.这些准备工作完成以后就会开始调用Add函数了,在这个地方我们首先会往上压一个地址,这个地址可以把它理解为是Add函数的一个入口地址,我们压这个地址的目的是,我们从这个地址进去使用Add,出来也从这个地址出来。如果我们没有这个地址就会导致,我们进去Add函数使用完以后,不知道把得到的值从哪里返回出来。

12.下面就是调用的过程,往栈顶压一个ebp-main(这个ebp-main来自底下的ebp,可以把这个ebp-main理解成是ebp的”影分身”,充当ebp的作用,作为一个维护寄存器),将esp再次提升到ebp-main的高度,然后esp往上升,此时ebp-main和esp之间的空间就是为Add开辟的函数栈帧空间。开辟了以后还是一样,在Add函数栈帧栈顶上面堆3个寄存器ebx、esi、edi,然后还是一样我们每压一个寄存器,esp就会往上升一个单位,最后将edi以下所有的Add函数栈帧空间里的内存全部初始化为cc,初始化完以后,就在Add函数中开创变量z,并且初始化为0。随后我们将刚刚的ecx寄存器中的10放到eax寄存器的20中,然后两者相加,最后将这个算出来的30放到变量z中。

 

 

 

 

 

最后:函数栈帧的销毁

接着我们返回我们的z,将z里的30放到我们的eax寄存器中,然后我们的Add函数栈帧就开始慢慢地销毁了,从上往下依次弹出我们的edi、esi、ebx寄存器,同时每弹出一个寄存器我们的esp也会往下走一个单位,当走到Add函数栈顶的时候,然后将esp再拉回到ebp-main这个位置,拉完以后,ebp-main弹出,”影分身”消失回归到本体,也就是main函数栈底的ebp,同时esp回到之前我们压的Add函数的入口地址处。再接下来esp回到我们的main函数上面的edi寄存器栈顶处,将我们寄存器中存的临时变量20和10还给我们的操作系统。最后就是将寄存器eax中的30赋给我们main函数中的变量c,至此函数栈帧的创建和销毁过程大致就是这样了,欢迎大家继续补充和指正。 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值