C语言拯救者 番外篇 (函数栈帧的创建和销毁讲解)

目录

1.寄存器

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

返回值是怎么带回来的?

 1.局部变量是怎么创建的

2.为什么局部变量不初始化的值是随机值

3.函数是怎么传参的?传参的顺序是怎样的?

4.形参和实参是什么关系?

5.函数调用是怎么做的?

6.函数调用的结果怎么返回的?


注:以下环境是基于vs2013进行展示

大家学习的过程中应该会有许多疑惑,举几个例子:

·局部变量是怎么创建的?

·为什么局部变量没初始化的值是随机值?

·函数是怎么传参的(传参的顺序是怎样的)?

·形参和实参是什么关系?

·函数调用是怎么做的?

·函数调用结束后是怎么返回的?

接下来就带大家把问题全过一遍



1.寄存器

下面我们会遇到的寄存器有:eax,ebx,ecx,edx

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


首先我们要理解,每一个函数调用,都需要在栈区上开辟一块空间

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

int main()
{
	int a = 10;
	int b = 20;
	int c = 0;
	c = Add(a, b);

	return 0;
}

开始调用main函数,为main函数分配空间,调用哪个函数,ebp和esp就去维护那个函数的函数栈帧。

我要调用Add函数,ebp和esp跑去维护Add函数,ebp和esp之间的空间就是为Add函数调用所开辟的空间,就叫函数栈帧

由于栈区的使用习惯是先使用高地址,再使用低地址,放数据的时候,从顶上往下放数据,所以esp可以理解为栈顶指针,ebp理解为栈低指针。

大家有没有疑惑过,main函数被调用起来了,但是main函数是被谁调用的?

往下走,将代码执行完,可以看见__tmainCRTStartup,这个函数内部调用了main函数 ,说明main函数也是被别人调用的,__tmainCRTStartup又是被tmainCRTStartup调用

在main函数之前也应该分配这两个函数的空间 

 

大概轮廓是这样,具体是怎么调用的接下来研究


进入反汇编

main函数也是被别人调用,那调用main函数的__tmainCRTStartup已经被创造好了空间


反汇编第一句 (push压栈:给栈顶放一个元素)(pop出栈:从栈顶删除一个元素)

ebp存的是__tmainCRTStartup栈低的地址,push叫压栈,给栈里面放元素

push完后,esp的地址也应该改变

 监视

观看esp和ebp,这是初始地址

当我们开始执行push后 ,地址减了4

内存

可以看见, ebp确实被push压进去


反汇编第二句话

 mov指令是把后面的值赋到前面,把esp给ebp

 


反汇编第三句话

 sub是减法,给esp减去0E4h,0E4h转化为十进制为228

监视(esp地址发生改变,为main函数开辟了一块很大的空间)

        


反汇编第四,五,六句话


 反汇编第七,八,九,十句话,lea =load effective address(加载有效地址)

 把后面有效地址加载到edi里面,这句话不太好观察,我们显示符号名后变成ebp-0E4h

 

 mov  把39h放到ecx里面去。 把0CCCCCCCCh的值放进eax里面

rep stos是指,要把刚刚从edi这个位置开始,向下的39h次这么多空间dword的数据内容,全部改成0CCCCCCCCh。

一个word是两个字节,dword是四个字节

从edi开始,到ebp停止,这么多空间内容全部初始化为CCCCCCCC

内存监视

 


此时main函数栈帧已经开辟好了,下面正式开始执行代码

mov,把0Ah(十进制就是10)放到ebp-8的位置,我们认为CCCCCCCC为四个字节,为a开辟的空间就是ebp到ebp-8空间

我们是创建变量的时候赋值,如果创建变量是没有赋值,默认里面放的就是CCCCCCCC

打印烫烫烫的原因就是因为里面是随机值  CCCCCCCC,不进行初始化,不同的编译器里面可能放的是不同的值

 

内存监视a

0a 00 00 00是因为此编译器是小端存储

 内存监视b

14h是十六进制,转化为10进制是20,空了两个整形的位置

内存监视c

又是相差两个整形的位置

 


函数调用

 函数调用要传参,下面这两个动作是在传参么?

mov,把ebp-14h(也就是b)放到eax里面。push->eax,压栈

mov,把ebp-8(也就是a)放到ecx里面。push->ecx,压栈

答案确实在传参

 call指令是调用函数,现在我们记住call指令的地址

执行完call指令,AA8地址放的是00 c2 14 50

压栈了call指令的下一个地址 ,为什么要记住这个地址?不要着急

 call一调用后马上调用Add函数,Add函数调用完后需要返回,我需要回到call指令的下一条指令(call指令执行完)


进入函数内部(前几步的操作顺序跟前面main函数一样)

为Add函数准备栈帧

首先push ebp(此时ebp还在维护main函数栈低) 把ebp值压到顶上 

内存监视(ebp压栈在call指令下一条指令地址)

把esp的值给ebp

esp,0CCh是在为Add这次调用分配函数栈帧空间

 


函数加载地址

lea 把[ebp+FFFFFF34h]地址加载到edi里面,mov把33h 放到ecx里面,再把0XCCCCCCCCh的值放到eax里面

rep ->从edi开始向下到ebp之间所有位置初始化为0XCCCCCCCCh

 


执行计算任务

 把0放到ebp-8的位置


执行加法,但是x,y在哪里?

把ebp+8的值放到eax里面,ebp+8找到了ecx

ecx是压栈压来的,我们可以叫他a‘,eax就是b'

把ebp+8的值放到eax里,再add把ebp+0ch的值加到eax里面

加法完成,eax变成30


形参是怎么来的?我们有主动创建形参么?

没有,是因为我们在下面刚开始调用函数的时候就把参数传过来了

通过mov push mov push指令传参

push压栈先压b,再压a。c=Add(a,b)先传的b,在传的a,参数是从右向左传的

形参不是在Add函数内部创建的,而是回来找了压栈的空间 数据进行计算

我们经常说,形参是实参的一份临时拷贝,这句话完全正确,改变形参不影响实参


函数返回

 

大家有没有疑惑, return z返回的时候,z不是销毁了么,怎么能够把结果带回去

 return z的意思是把ebp-8的值放到eax里面,eax是寄存器。寄存器是不会程序退出销毁的

ebp-8位置是z

pop三句话,弹出,把栈顶元素弹出放到edi里面,每次弹出esp就++往下走

mov 把ebp赋给esp,esp没有指向栈顶了,指向ebp所在的位置(回收Add函数的栈帧)

pop一下,之前存了ebp-main函数的栈低地址(为了防止函数销毁后找不到栈低指针),ebp返回到main函数栈低地址

 

 


此时pop弹出后,ebp回归到main函数栈低

ret指令,esp回到了00C21450的地址,此地址是call指令下一条指令的地址,继续从call指令下一条开始执行(不仅要走的出去,还要回的来)

给esp加8,跳过形参a,b,形参a,b销毁

 把eax的值放到ebp-20h,ebp-20h位置就是c的空间 ,eax值是和:30

返回值是怎么带回来的?

是我们首先放到eax寄存器里面,当我们回到函数放到局部变量c里面去

 


 1.局部变量是怎么创建的

首先为函数分配好栈帧空间,栈帧空间初始化好一部分空间以后,给局部变量在栈帧里面分配空间

2.为什么局部变量不初始化的值是随机值

随机值是编译器放进去的,例如CCCCCCCC,如果初始化便把随机值覆盖

3.函数是怎么传参的?传参的顺序是怎样的?

当我们还没有去调用函数的时候,便已经push从右向左压栈压进去,当我们进入形参函数的时候,在Add函数里面,通过指针的偏移量回来找到了我们形参。

4.形参和实参是什么关系?

形参实在压栈的时候开辟的空间,和实参值是相同的,空间是独立的,形参是实参的一份临时拷贝,改变形参不影响实参

5.函数调用是怎么做的?

上面总结清楚

6.函数调用的结果怎么返回的?

调用之前就把call指令下一条指令的地址记住,把ebp调用函数的上一个函数的栈帧存进去,当函数调用完后返回的时候,弹出ebp,就能找到原始上一个函数的ebp,指针往下走就能找到esp地址

返回值是通过寄存器带回来的

函数的内部所创建的静态变量是在全局开辟的,今天我们画的都是在栈区上开辟的

  • 72
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 18
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

北方留意尘

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

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

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

打赏作者

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

抵扣说明:

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

余额充值