函数栈帧的创建与销毁

寄存器

在讲函数栈帧之前先来补充一下知识~

eax、ebx、ecx、edx

ebp、esp  这两个寄存器中放的是地址,这两个地址是用来维护函数栈帧的。

函数调用前的准备

在VS2013中,main函数也是被其他韩数调用的:mainCRTStartup->_tmainCRTStartup->main。

mainCRTStartup先调用_tmainCRTStartup,再通过_tmainCRTStartup调用main函数进行栈上空间的开辟。

注意:每一次函数调用,都要在栈区上创建一个空间。 

举例代码

我们以这样的代码为例:

#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 c = 0;
	c = Add(a, b);
	printf("%d\n", c);
	return 0;
}

栈创建的实现

                              

 先来解释一下几个代码意思:

push:压栈,给栈顶放一个元素。

pop:出栈,从栈顶删除一个元素。

sub:相减运算。

mov:传值。

lea:取偏移地址。

首先进行了压栈,将栈底指针ebp压在_tmainCRTStartup开辟空间的上面。

紧接着mov后,esp指向的位置和ebp的位置相同。

sub后,esp减去一个地址后得到了新的地址,会重新指向新的地址,这个地址并且比原来变小了

push:接着压栈,将ebx、esi、edi放入栈顶。

接下来的三行的作用:

001C18BF  mov         ecx,9  
001C18C4  mov         eax,0CCCCCCCCh  
001C18C9  rep stos    dword ptr es:[edi]  

将ebx到ebp也就是main函数内部的空间每一个地址用“cccccccc”填充。

                                          

 main函数栈帧内部实现

 

        紧接着在ebp-8的地址所指向的空间赋值a=10、ebp-16的地址所指向的空间赋值b=20、ebp-24的地址所指向的空间赋值c=0。

        注意:发现每次开辟好的栈空间里的赋值都要间隔2个地址,这是在VS环境下的特点,不同编译器有不同的特点,并且为什么要这么做呢?有什么意义呢?具体详情可以看我相关的博客~

经过上述实现后:

                         

 通过调试我们发现,main结束完之后并没有直接进入到Add函数的调用。

而是进行接下来的步骤:

mov   eax,dword ptr[ebp-14h]
push  eax
mov   ecx,dword ptr [ebp-8]
push  ecx

mov:将ebp-14h地址对应的内容放到eax寄存器中

push:压栈

mov:将ebp-8h地址对应的内容放到ecx寄存器中

push:压栈

       经过这样的操作后,相当于把main函数里面的b和a的值传到了新开辟的一块空间。在这里我们就可以清晰的明白,函数传值调用后,新开辟的空间用形参接收传过来的参数。原来的a和b属于实参,形参和实参属于两块不同的空间,形参的改变不会影响实参的改变。

用图来来表示这过程:

               

 注意:从上面我们可以看出,传参比压栈先行。Add函数还没有在栈区上开辟空间,参数就已经传传给相关的寄存器指针,也就是说形参不在Add函数栈空间的内部。

再紧接着F11调试

                 

 调用call指令,call指令里面存了下一条指令的地址。这样可以保存mian函数栈帧的状态,也方便调用完Add之后返回main函数的栈帧。可以认为:现场保护和现场恢复。

接下来是Add函数内部的实现。在ebp-8的位置上对int z=0;进行开辟空间。

mov   dword ptr [ebp-8],0
mov   eax,dword ptr [ebp+8]
add   eax,dword ptr [ebp+0ch]
mov   dword ptr [ebp-8],eax

mov:创建z

mov:取a放到eax中

add:把b加a后放到eax里面(原来eax放过b的值了)

mov:把z里面的值放到eax寄存器中,通过函数返回值eax带回。

具体图示如下:

栈帧的销毁

Add函数调用完之后开辟的空间就会销毁。

pop   edi
pop   esi
pop   ebx
mov   esp,ebp
pop   ebp
c 

 pop:edi出栈

 pop:esi出栈

pop:ebx出栈

mov:ebp赋值给esp,esp指向ebp指向的位置,Add函数的栈帧就销毁了。

pop:ebp出栈

ret:ret指令会使得出栈一次,并将出栈的内容当作地址,将程序执行跳转到该地址。

ret执行完之后,汇编代码跳转到刚才call指令的下一条指令的地址。 

                  

  然后打印c的值,main函数结束之后销毁main函数的栈帧.

在return 0之前的命令:add esp 8->相当于将esp向下移动两个地址。从而此时的位置是:    

  

 main函数栈帧的销毁

00C31910  pop         edi  
00C31911  pop         esi  
00C31912  pop         ebx  
00C31913  add         esp,0E4h  
00C31919  cmp         ebp,esp  
00C3191B  call        00C31244  
00C31920  mov         esp,ebp  
00C31922  pop         ebp

 类比于Add函数栈帧的销毁,对main函数栈帧空间进行销毁。

如图:

最后执行ret指令,汇编代码回到_mainCRTStartup栈帧处,函数调用全部结束。

总体的草图大致如下:

  

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值