图解函数栈帧的创建和销毁全过程

这篇博客主要是对局部变量以及函数的一些问题的解答

  1. 局部变量是如何创建的?
  2. 局部变量在没有初始化时是随机值,背后原理是什么?
  3. 为什么说形参是实参的临时拷贝?
  4. 函数是如何调用参数的?
  5. 局部变量出函数就会销毁,那么函数返回的值是如何传递到main函数中的?

上述问题在函数的栈帧创建到销毁的过程中可以找到答案,这个过程涉及到汇编语言,我尽量简洁的说明每一条指令在做什么

注:不同的编译器此过程会有差异

以下列代码为例,来展示函数的栈帧从创建到销毁的全部过程

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

目录

1.寄存器

2.main函数的创建

3.创建变量

 4.调用函数

4.1传参

4.2开辟函数空间

 4.3函数的计算

5.返回main函数

5.1Add函数的返回值

5.2函数销毁

5.3返回值的传递


1.寄存器

在展示之前需要了解什么是寄存器,寄存器是CPU的组成部分,是有限存贮容量的高速存贮部件,可用来暂存指令、数据和地址等。这里主要介绍后面内容会使用到的寄存器:

  • ebp:基址指针寄存器,常用它作为基址来访问堆栈
  • esp:堆栈指针寄存器,堆栈的栈顶指针

这两个寄存器存放的都是地址,二者的地址用于维护函数栈桢

  • ebx:基址寄存器,常以它为基址访问内存
  • esi:来源索引寄存器,常用做内存数据指针和源字符串指针
  • edi:目的索引寄存器,常做内存数据指针和目的字符串指针
  • eax:累加寄存器,常用于加法、乘法和函数的返回值

2.main函数的创建

在写好代码后我们进入调试,来看看程序在创建main函数之前在干什么

首先,main函数是由其它的函数调用的,如图:

mainCRTStarturp调用__tmainCRTStartup,然后__tmainCRTStartup调用main函数,所以在main函数之前在栈帧上就已经为两个函数开辟出了空间

 调用main函数后来看一下汇编代码

在main函数和第一个变量之间相隔多条汇编代码,我们逐个来解读

 完成这三步后,就在栈区为main函数开辟出了一块空间

 

 操作完成后从ebp到edi之间的空间全部被初始化为了CCCCCCCC

 

 到这里第二个问题就有了答案,因为main函数空间内在创建变量之前就已经初始化了,不同编译器的初始化的值不同,变量在未初始化时是随机值

3.创建变量

main函数和变量都创建完毕后,接下来就是调用函数了

 4.调用函数

4.1传参

还是逐个来解读,先看前4行

 

 也就是说,形参的空间并不是在函数内部开辟的

传参后就是调用函数

 

在调用函数之后,便进入了函数Add内部,call下方的两条指令会在函数返回的时候执行

4.2开辟函数空间

同main函数一样,进入Add函数后首先要给函数开辟空间,可能空间的大小以及初始化有所不同,但操作一样

 4.3函数的计算

 

ebp+8和ebp+12就是传参时的地址

在计算完毕后,接下来就是返回main函数了

5.返回main函数

5.1Add函数的返回值

 所以为什么即使变量销毁,但是值依旧能够传递给main函数?

是因为变量的在函数销毁前存放在了寄存器中,寄存器不会被销毁,除非你物理销毁CPU

(〃 ̄ω ̄〃)

5.2函数销毁

 结合上图来看,ebp指向的地址中存放的是main函数的地址,所以在弹出ebp地址后,ebp返回main函数,并指向栈底

此时esp指向的地址中存放着一个地址,下一条指令

返回,那么esp就返回00C21450这个地址

 00C21450是由指令call记录的,是指令call的下一条指令,故esp返回的是call的下一条指令

5.3返回值的传递

call下一条指令esp+8,那么eax和ecx的空间就弹出了

eax存储的是Add销毁前z的值,ebp-20处存储的是变量c,所以到这返回值传给了c

 那么函数栈帧的创建到销毁的过程便介绍完了,来回答一下开头提出的问题

1.局部变量是如何创建的?

在开辟出main函数的空间后,将main函数的空间分配给变量

2.局部变量在没有初始化时是随机值,背后原理是什么?

main变量在进入main函数之前,main函数内部的空间就已经填充了值,不同的编译器的值不同

3.为什么说形参是实参的临时拷贝?

因为函数调用参数的时候并不是在main函数内部调用,而是提前在main函数外将参数的值放入寄存器进行压栈,然后调用,二者虽然值相同,但地址不同

4.函数是如何调用参数的?

在开辟函数空间之前函数的参数就已经放在寄存器中进行压栈了,在函数需要使用的时候直接去寄存器的地址将值取出

5.局部变量出函数就会销毁,那么函数返回的值是如何传递到main函数中的?

返回值提前保存在寄存器中,返回时虽然变量销毁,但寄存器不会被销毁,返回main函数后需要使用返回值时再从寄存器中取出

如果上述过程有遗漏或错误的地方,请指出

 

 

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星鸦wyk

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值