函数栈帧的创建与销毁

前言

       前期学习的时候,我们可能有许多困惑:

  • 局部变量是怎么创建的?
  • 为什么局部变量的值是随机值?
  • 函数时怎么传参的?传参的顺序是怎样的?
  • 形参和实参是什么关系?
  • 函数调用是怎么做的?
  • 函数调用结束后是怎么返回的?

       当你了解了函数栈帧的销毁与创建之后,以上这些问题你就可以想明白了。

函数栈帧的创建和初始化

       进入正题:今天使用的环境是2013,不要使用太高级的编译器,因为越高等级的编译器,越不容易观察和实现。同时在不同的编译器下,函数调用的过程中栈帧的创建是略有差异的,具体细节取决于编译器的实现。

       今天我们将会遇到如eax、ebx、ecx、edx、ebp、esp等这几个寄存器。它是独立于内存的,它集成到CPU上面。其中ebp、esp这两个寄存器很重要,因为它们是用来维护函数栈帧的。

       每一次函数调用,都要在栈区创建一个空间,假如我们写上一个代码:

int Add(int x, int y)
{
	int z = 0;
	z = x + y;
	return z;
}

int main()
{
	int a = 10;
	int b = 20;
	int c = Add(a, b);
	printf("%d\n", c);
}

       这个代码十分的简单,每一步执行的细节都给细化了出来,这样更方便我们观察。今天我们就以这个代码来理解函数的栈帧的创建与销毁。

       每一次调用函数的过程中,编译器都要在内存中的栈区上为该函数开辟一块空间。我们称这一块空间为该函数的栈帧。

       它是怎么被维护的呢?

       它由两个寄存器来维护:ebp和esp。寄存器里面存储着地址,那么ebp就存储着栈帧的高地址,esp存储着低地址。正在调用哪个函数,它们两个就指向这块栈帧的两端。

        在vs2013中,main函数也是被其他函数调用的,它是被__tmainCRTStartup这个函数调用。而__tmainCRTStartup函数又被mainCRTStartup这个函数调用。

       我们按下f10,开始调试,鼠标右键点击转到反汇编按钮,我们就可以看到每一条语句对应的反汇编代码。

       当程序进入main函数之后,编译器就开始为main函数创建栈帧。但此时ebp、esp还在维护着__tmainCRTStartup这个函数。通过一系列的汇编指令,就可以将两个寄存器移动到新的位置,开始维护main函数。下面我们就来分析一下以上这些汇编代码的含义。

       第一条语句是push,意思是压栈,将ebp这个值压入栈中。

       然后esp就不再指向原来的位置,它会向上走一格,它会始终指向栈顶。

       打开监视:我们可以看到

       走完这个push,发现它的值已经改变:

       打开内存,我们就可以看到:

       esp的值为008ffba4,这个地址对应的就正好是ebp的值008ffbf4。

       接着来看下一条指令:

       mov的作用是赋值,将esp赋给ebp。那就表示ebp现在指向的和esp指向的位置是同一个位置。

       接着看下一条指令:

       sub就是减的意思,将esp减去0E4h,0E4h是一个八进制数字,十进制是228。它减去了这个数字,它就会向上移动,指向上边的某一个位置。其实上面就是为main函数开辟的栈帧。

       然后继续往下看汇编代码,我们遇到了三次push。编译器往栈里面压了三个元素:ebx、esi、edi。这三个值无关紧要。

       再往下看汇编指令,我们遇到了lea,它的全名为load effective address,意思是加载有效地址,就是把[ebp-0E4h]这个值放到edi里面去。

       再往下看汇编指令,我们遇到了两个mov,第一个mov是将39h放在ecx中去,第二个mov是将0CCCCCCCCh放在eax里面去。

       而下面的一条汇编指令rep stos才是比较重要的,它的意思是从edi开始,往下39h个dword的内容改成cccccccc,而dword表示四个字节(word是两个字节),也就是往下多少行(这里的一行包括4个字节)的内容全部改成cccccccc。

       当程序执行过后,再来看监视:

       可以看到,有非常多的空间内容被改变了。也就是ebx以下,ebp以上的空间。

局部变量的创建

       我们再来往下看:

       第一个mov是将0Ah,也就是10(十进制)放到ebp-8这个位置

       第二个mov是将14h,也就是20(十进制)放到ebp-14h这个位置

       第二个mov是将0,也就是0(十进制)放到ebp-20h这个位置

       当我们没有给变量初始化,那么里面的值就是cccccccc,这也就是为什么当程序出错时会打印出cccccccc或者“-52 -52 -52”等没用的数据。所谓初始化,就是将原来的数据释放掉。

       变量间的间隔大小由编译器决定。不同的编译器会略有差异。

       以上就是局部变量的创建过程。

函数的调用和形参的创建

       之后程序就走到了函数调用部分。

       第一条汇编指令时mov,把ebp-14h赋给eax中。而ebp-14h就是我们的变量b。

       第二条指令时push,也就是压栈,将eax压入栈中。

       第三条语句是将ebp-8的值赋给ecx,而ebp-8的值就是变量a的值。

       第四条语句是将ecx压入栈中。

      上边的四条指令其实做的操作是传参。它们都是形参。它们的传参顺序是从右向左传的,先传b,再传a。

       然后下一条指令时call指令,是跳转指令,跳转至00C20E1地址处,同时向栈中压入call指令的下一条指令的地址。因为当call执行完后,要回到call指令的下一条指令处。编译器将这个地址记住,当call回来的时候就找到这个地址,继续往下执行。

       执行完call指令后,程序就跳转到了Add函数里面。

       而上图的这些汇编代码,跟main函数一样,是在为当前函数开辟栈帧用的。

       第一条指令,是将ebp给压入栈顶。

       第二条指令,是将esp赋给ebp。

       第三条指令,是将esp减去一个值。

       前三条指令执行完毕,栈帧就变成了这个样子:

       下面又是三个push,后面四个也是初始化:

       初始化完成,就开始创建局部变量:

       mov指令是将0放进ebp-8这个位置当中。

       下面就开始执行z = x + y 这条语句。x和y此时就在main函数的栈帧里面。

       第一个mov是将ebp+8的值赋给eax。

       第二个add是将ebp+0Ch,也就是20,加到eax里面去,就是30。

       第三个mov是将eax放到ebp-8里面去。

函数的返回值和函数栈帧的销毁

       接下来开始返回值。

       第一个mov是将ebp-8赋给eax,而eax是寄存器,不会因为函数的退出而销毁,因此它能够保存返回值。

       之后又三个pop,代表出栈。分别将edi、esi、ebx给出栈了。每出栈一次,esp就向下移动一格,始终指向栈顶。

       然后mov操作,是将ebp赋给esp。ebp和esp不再维护Add的栈帧,Add函数栈帧销毁。

       然后pop,将ebp弹出去并赋给ebp,ebp中存放着main函数的栈底指针,赋给ebp后ebp就指向了main函数的栈底。同时esp也指向了main函数的栈顶。重新维护main函数的栈帧。

       ret指令就是退出Add函数并回到main函数中call指令的下一条指令,同时弹出这个地址。

       当从Add函数中退出之后,程序回到了main函数。它应该从call指令的下一条指令开始执行。

      

       当程序回来之后,此时esp指向栈顶,栈顶此时存着形参a和b,但此时它们已经没有用处了,于是便执行下一条指令。

       这个add指令使esp加8,向下移动两格,也就是八个字节,将两个形参空间给释放掉了。继而证明了形参是实参的临时拷贝,随着函数的退出,形参的生命周期就结束了。

       下面的一条mov指令,将eax赋给ebp-20h这个位置处。而这个位置处存储着c。所以c就存储着Add函数的返回值。以上就是main调用Add函数获得返回值的过程。

  • 23
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值