函数栈帧的创建和销毁

我们学习函数的时候总会有诸多疑惑:

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

接下来来带大家解答疑惑:

使用的编译环境是VS2013,不同的环境会有差异

寄存器:

eax

ebx

ecx

edx

ebp 

esp

函数栈帧由这两个寄存器维护

ebp 栈底指针

esp 栈顶指针

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

注意:

每一个函数调用,都要在栈区创建一个空间

栈区的习惯是先使用高地址再使用低地址,由高指向低

是往上使用的

压栈 从栈顶放入一个元素 push

出栈 从栈顶删除一个元素 pop

 

main函数也是被其他函数调用的

__tmainCRTStarup调用

mainCRTStarup

也为这两个函数调用空间

 

边调用边分配栈帧

具体研究如何调用

F10 右击鼠标反汇编 就可以看到C语言的汇编代码

 

去掉显示符号名,就只显示地址,好观察 (右击鼠标)

 

接下来,我们开始解读,反汇编代码

在调用main函数之前,是先调用__tmainCRTStarup函数的

有ebp和esp来维护,所以首先会先开辟一块空间给__tmainCRTStarup函数

#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;

}

提示:按照代码顺序写,但是程序执行是先进入main函数的,所以先看main函数的解析

将代码加粗标红

int Add(int x, int y)

{

003913C0 push ebp

压栈来自main函数的ebp

esp往上走

003913C1 mov ebp,esp

赋值 ebp指向ebp

003913C3 sub esp,0CCh

esp往上走

为Add函数分配栈帧

003913C9 push ebx

003913CA push esi

003913CB push edi

三次压栈 esp跟着往上走

003913CC lea edi,[ebp+FFFFFF34h]

003913D2 mov ecx,33h

003913D7 mov eax,0CCCCCCCCh

003913DC rep stos dword ptr es:[edi]

初始化 cc cc cc cc

开始执行计算任务

int z = 0;

003913DE mov dword ptr [ebp-8],0

z = x + y;

003913E5 mov eax,dword ptr [ebp+8]

将[ebp+8]的的地址值的值放入eax中,[ebp+8]就是刚才的值10 ecx

003913E8 add eax,dword ptr [ebp+0Ch]

把[ebp+0Ch(12)] 中的值(20)加到eax里面去

所以此时eax中存放的值是30

003913EB mov dword ptr [ebp-8],eax

把eax中的值放到 [ebp-8]这个位置

此时会发现形参根本不是在Add内部创建的,而是回到找当初调用时候压栈进去的空间a和b

所以说,形参是实参的一份临时拷贝

之前都是在说函数的调用,现在开始返回:

return z;

003913EE mov eax,dword ptr [ebp-8]

把[ebp-8] 里面的值 30 放到eax中去

为什么要这么做?

因为程序接着走之后,出Add函数z的值(局部变量)就跟着销毁了,所以要把它的值放到eax中

eax是一个寄存器,寄存器是不会随程序的退出而销毁的

}

003913F1 pop edi

003913F2 pop esi

003913F3 pop ebx

弹出,每次弹出esp都会++一次,往下走

003913F4 mov esp,ebp

回收空间,把ebp的值赋给esp,esp就会往下走

003913F6 pop ebp

弹出的是ebp—main

意味着ebp往下走找到原先的ebp

此时就会回到main函数里

003913F7 ret

跳到原来main函数里保存的call指令的地址的下一条指令add上去

int main()

{

先push(压栈)

首先push ebp

esp地址值变小了代表着(图中)往上走了,因为是从高到低所以地址值会变小

mov 赋值

把esp的值给ebp 往上走

sub 减法

给esp-0E4h(八进制数 228)

意味着esp地址值变小了,就往上走指向上面的某一区域

此时esp与ebp都变了,就相当于进入了main函数,给main函数预开辟了一块空间,这块空间很大(编译器决定)

接下来就要使用这块空间

接下来压栈三次,开辟的空间在main函数空间之上

push ebx

push esi

push edi

此时esp已经指向edi,ebp还是指向ebp

lea edi ,[ebp-0E4h]

load effective address 指向有效地址

使ebp指向ebx 往上走,减法都是往上走

mov move移动

mov ecx,39h ecx是次数

mov eax,0CCCCCCCCh

rep stos dword ptr es:[edi]

把从edi开始向下的39h空间dword(doubleword 双字 四个字节)全部改成0CCCCCCCCh

这个动作意味着把内存中所有的值全部初始化成 cc cc cc cc

int a = 10;

mov dword ptr [ebp-8],0Ah

把0Ah(10)这个八进制数放到[ebp-8]的位置

说明为a开辟了ebp到[ebp-8]的一块空间

如果定义一个变量,初始化的时候不给赋值,此事内存中放入的就是cc cc cc cc(随机值)

所以这就是有时候我们会打印出烫烫烫烫烫烫这类乱码的原因

放置的位置取决于编译器

int b = 20;

mov dword ptr [ebp-14h],14h

int c = 0;

mov dword ptr [ebp-20h],0

当abc创建好之后,开始调用Add函数

c = Add(a, b);

mov eax,dword ptr [ebp-14h]

把[ebp-14h] 里面的值(20 b)放到eax里去

push eax

压栈20

注意:每一次压栈push都是压到栈顶,如此刻eax就是栈顶

mov ecx,dword ptr [ebp-8]

把 [ebp-8] 里面的值(10 a)放到ecx里去

push ecx

压栈10

刚刚这两个动作是在传参,实参

调用的时候就已经开始传参了,参数从右向左传

0039144B call 003910E1

调用

注意:此时压栈的是call指令的下一条指令add的地址00391450

原因:记住下一条指令的地址,因为此时需要跳出main函数进入Add函数,所以记住地址方便下一次跳回之后接着往下继续走

此时程序会跳往Add函数里面去,就不执行下面的了,此时才算真正来到Add函数里面

跳到这里(不仅要走出去,还要回得来)

00391450 add esp,8

esp+8 往下走 指到edi

mov dword ptr [ebp-20h],eax

把eax中的值30,放到 [ebp-20h] c 中去

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

00391456 mov esi,esp

00391458 mov eax,dword ptr [ebp-20h]

0039145B push eax

0039145C push 395858h

00391461 call dword ptr ds:[00399114h]

00391467 add esp,8

0039146A cmp esi,esp

0039146C call 0039113B

return 0;

00391471 xor eax,eax

}

结合图一起看比较清晰

 

注意:寄存器是集成到CPU上的

计算机的硬盘、内存、寄存器都是独立存在的

评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Hey pear!

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

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

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

打赏作者

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

抵扣说明:

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

余额充值