C语言中函数栈帧的创建与销毁,局部变量的创建


首先,给出一段简单的代码

#include <stdio.h>
int Add(int x, int y)
{
	int z = 0;
	z = x + y;
	return z;
}
int main()
{
	int a=42;
	int b=35;
	int c=0;

	c = Add(a, b);

	printf("%d\n", c);

	return 0;
}

代码实现了一个简单的函数,加法函数。

函数栈帧简单概念

每一个函数调用,都要在栈区指定一块空间,而这块栈空间就被称为函数栈帧。每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量
函数栈帧由两个寄存器中存放的指针所确定。

寄存器

在此简单提一下寄存器。寄存器是有限存储容量的高速存储部件,它们可用来暂存指令、数据和地址。此处函数栈帧由esp和ebp寄存器确定空间范围。
下文还会用到的寄存器还有eax,ebx,ecx,edx这4个寄存器。
esp中存放的指针指向栈顶,ebp中存放的指针指向栈底。两个寄存器之间的内存空间就是函数栈帧。以最开始给出的加法函数为例,看图演示:
函数栈帧部分描述

函数栈帧的创建与销毁

接下来通过分析上述代码的汇编代码来讲述函数栈帧的主要创建过程。

  push        ebp  
  mov         ebp,esp  
  sub         esp,0E4h  

push意为压栈,即将某元素进入栈顶。第一行指令意为,将ebp的值放到栈顶,此时因为有新元素入栈,栈顶发生改变,且esp始终指向栈顶元素,故esp指向ebp值对应的位置。图示如下:
push    ebp
因为main函数也是被调用的,此处是被invoke_main的函数调用,因此,在调用main之前,esp和ebp在维护invoke_main的空间。

mov ebp,esp意为,将esp中的数据放入ebp中,因为esp中存放着指针,mov完成后,ebp就和esp一起指向同一元素。图示:
在这里插入图片描述

sub esp,0E4h ,sub是减法指令,指令意为esp=esp-0E4h
栈区空间使用特点为,先使用高地址,再使用低地址。此处esp值减小,则esp向上移动,指向新位置。而ebp留在原来的位置,esp和ebp之间出现了间隔,这个间隔就是两者维护的空间,即main函数的函数栈帧。
在这里插入图片描述

 push        ebx  
 push        esi  
 push        edi   

三个元素依次压栈,esp也依次指向栈顶元素。
在这里插入图片描述

 lea         edi,[ebp-24h]  
 mov         ecx,9  
 mov         eax,0CCCCCCCCh  
 rep stos    dword ptr es:[edi]

lea全称load effective address,加载有效地址
ecx中存入9h,eax中存入CCCCCCCC
这4条语句意为,将从edi开始向下ecx个dword(double word双字,一字为2个字节,dword为4个字节)初始化为eax中的内容。
即从edi开始,将4个字节的数据初始化为CCCCCCCC,进行9次这样的操作
在这里插入图片描述

    int a=42;
mov         dword ptr [ebp-8],2Ah  
	int b=35;
mov         dword ptr [ebp-14h],23h  
	int c=0;
mov         dword ptr [ebp-20h],0

将abc要赋的值,存入对应分配给abc的指定空间。
在这里插入图片描述

  	c = Add(a, b);
  mov         eax,dword ptr [ebp-14h]  
  push        eax  
  mov         ecx,dword ptr [ebp-8]  
  push        ecx

对照上图知,ebp-14h为b,ebp-8为a。以上指令,就是在传参。先传b的值,再传a的值,b,a分别放入寄存器eax和ecx并分别入栈,需要说明的是,传参过程中,参数存储在寄存器中,而并非在Add函数内部,图示如下:
在这里插入图片描述
然后执行call指令
在这里插入图片描述

  push        ebp  
  mov         ebp,esp  
  sub         esp,0CCh  
  push        ebx  
  push        esi  
  push        edi  
  lea         edi,[ebp-0Ch]  
  mov         ecx,3  
  mov         eax,0CCCCCCCCh  
  rep stos    dword ptr es:[edi]

以上指令是Add函数的部分。同main函数类似,通过几步完成Add函数栈帧的创建。如图
在这里插入图片描述

int z = 0;
  mov         dword ptr [ebp-8],0  
	z = x + y;
  mov         eax,dword ptr [ebp+8]  
  add         eax,dword ptr [ebp+0Ch]  
  mov         dword ptr [ebp-8],eax  
	return z;
  mov         eax,dword ptr [ebp-8]

ebp+8就是刚才传参时的a,即42,ebp+0Ch就是b,35,执行完的效果如图
将a+b的结果存入ebp-8。也就是说传参并没有传到Add函数内部,而是传到外面,需要时直接找到刚才传参的位置调用。
在这里插入图片描述
最后return z的时候,z的值存在ebp-8中,mov将z的值放入寄存器eax中,寄存器集成在CPU上,所以Add函数调用完成后,销毁局部变量,释放内存空间。z的值也正常存储,因为在函数结束前,已经拷贝到寄存器中了,寄存器是独立于内存的。

函数栈帧的销毁

  pop         edi  
  pop         esi  
  pop         ebx  

三个元素依次出栈,esp随之下移
在这里插入图片描述

mov         esp,ebp  
pop         ebp  
ret 

mov esp,ebp
函数完成,esp与ebp间隔消失,空间回收,函数栈帧随之销毁
在这里插入图片描述
pop ebp
pop意为出栈,ebp出栈,通过ebp–main中存储的数据,回到原来的位置
在这里插入图片描述
ret 栈顶元素出栈,通过栈顶存放的地址,转到对应地址的指令。即执行call指令下一行的指令。

007E18F1  push        ecx  
007E18F2  call        007E10B4  
007E18F7  add         esp,8  
007E18FA  mov         dword ptr [ebp-20h],eax 

add esp,8 ,刚才ret执行完,栈顶元素出栈,此时esp指向值为42的形参,esp+8,将两个形参所占空间释放。
mov dword ptr [ebp-20h],eax ,刚才将Add函数返回值存入了eax,eax再将值传入ebp-20h,即c。
在这里插入图片描述
最后依然是函数栈帧的销毁,上文已经讲过,此处不再赘述。

局部变量的创建

通过分析与观察,不难发现,main函数中的局部变量是在main函数栈帧创建之后,在函数栈帧内部规律地被分配空间。且因为在函数栈帧创建之初内部大量空间会被赋一个随机值,故在给局部变量赋值之前,局部变量存放的是一个随机值。局部变量在最终使用完之后,空间才会被释放掉。

笔者能力有限,如文章有问题,还请及时指正。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值