函数的调用过程(栈帧)

 


我们通过一段简单的代码来探究一下函数的调用过程:

#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("c = %d\n", c);
	return 0;
}

我们通过查看它的【调用堆栈】看看在main函数的调用:

 

 

我们可以发现main函数是在mainCRTStartup被调用的

每一次函数调用都是一个过程,这个过程通常称之为函数的调用过程。

这个过程要为函数开辟栈空间,用于本次函数的调用过程中的临时变量的保存、现场保护,这块栈空间被称之为函数栈帧

栈帧的维护中我们要了解两个寄存器ebp和esp

ebp存放了指向函数栈帧栈底的地址

esp存放了指向函数栈帧栈顶的地址

 

我们研究函数调用的过程,必须得对应汇编代码:

 

 

int main()
{
00D21800  push        ebp       //push 将 ebp 压栈处理
00D21801  mov         ebp,esp   //使esp的值赋给ebp,产生新的ebp
00D21803  sub         esp,0E4h  //给esp减去一个16进制数字,产生新的esp
00D21809  push        ebx  
00D2180A  push        esi  
00D2180B  push        edi  
00D2180C  lea         edi,[ebp+FFFFFF1Ch]  //lea(load effective address)加载有效地址
00D21812  mov         ecx,39h  
00D21817  mov         eax,0CCCCCCCCh  
00D2181C  rep stos    dword ptr es:[edi]  //从edi位置开始连续存储ecx次,存储内容为eax
	int a = 10;
00D2181E  mov         dword ptr [ebp-8],0Ah  
	int b = 20;
00D21825  mov         dword ptr [ebp-14h],14h  
	int c = 0;
00D2182C  mov         dword ptr [ebp-20h],0

我们可以在内存中看到他的局部变量的创建:

当不给局部变量值的时候,会产生一个随机值

接下来是add 函数的调用,参数传递过程:

 

在这里我们要简单了解一下函数调用约定(这个代码先传的参数是b,然后在传a):

函数调用约定,是指当一个函数被调用时,函数的参数会被传递给被调用的函数和返回值会被返回给调用函数。函数的调用约定就是描述参数是怎么传递和由谁平衡堆栈的,当然还有返回值。

参数传递顺序

1.从右到左依次入栈:__stdcall,__cdecl,__thiscall,__fastcall

2.从左到右依次入栈:__pascal

 

 

        c = add(a, b);
00D21833  mov         eax,dword ptr [ebp-14h]  //[ebp-14h]就是b,将b给eax
00D21836  push        eax                      //将eax压栈
00D21837  mov         ecx,dword ptr [ebp-8]  
00D2183A  push        ecx  
00D2183B  call        00D2111D                 //call 调用,先要压栈call指令下一条指令的地址,然后跳转到add函数的地方
00D21840  add         esp,8  
00D21843  mov         dword ptr [ebp-20h],eax

 

 

跳到add函数

按F11(VS环境)进入函数内部:

 

int add(int x, int y)
{
00D217B0  push        ebp  
00D217B1  mov         ebp,esp  
00D217B3  sub         esp,0CCh  
00D217B9  push        ebx  
00D217BA  push        esi  
00D217BB  push        edi  
00D217BC  lea         edi,[ebp+FFFFFF34h]  
00D217C2  mov         ecx,33h  
00D217C7  mov         eax,0CCCCCCCCh  
00D217CC  rep stos    dword ptr es:[edi]  
	int z = 0;
00D217CE  mov         dword ptr [ebp-8],0  
	z = x + y;
00D217D5  mov         eax,dword ptr [ebp+8]    //[ebp+8] 获取形参a的值
00D217D8  add         eax,dword ptr [ebp+0Ch]  //[ebp+0Ch] 获取形参b的值,然后将a,b相加之后放到eax中
00D217DB  mov         dword ptr [ebp-8],eax    //将eax放到[ebp-8]的位置,也就是z的位置
	return z;
00D217DE  mov         eax,dword ptr [ebp-8]    //将结果存在eax寄存器中,通过寄存器带回函数的返回值
}

 

 

int z = 0;

z = x + y;

函数的返回部分:

 

00D217E1  pop         edi  //pop出栈
00D217E2  pop         esi  
00D217E3  pop         ebx  
00D217E4  mov         esp,ebp  //将ebp赋给esp
00D217E6  pop         ebp      //出栈,将出栈的内容保存到ebp回到main函数的栈帧
00D217E7  ret                  //ret 指令会使得出栈一次,并将出栈的内容当做地址。将程序执行跳转到该地之处 

mov esp,ebp

pop ebp 将出栈的内容当做地址 (00D21840),之后回到main函数内部

执行完ret 之后:

 

00D21840  add         esp,8  
00D21843  mov         dword ptr [ebp-20h],eax  
	printf("c = %d\n", c);
00D21846  mov         eax,dword ptr [ebp-20h]  
	printf("c = %d\n", c);
00D21849  push        eax  
00D2184A  push        0D27B30h  
00D2184F  call        00D21339  
00D21854  add         esp,8  
	return 0;
00D21857  xor         eax,eax  
}
00D21859  pop         edi  
00D2185A  pop         esi  
00D2185B  pop         ebx  
00D2185C  add         esp,0E4h  
00D21862  cmp         ebp,esp  
00D21864  call        00D21127  
00D21869  mov         esp,ebp  
00D2186B  pop         ebp  
00D2186C  ret

 

add   esp,8

 

 

mov         dword ptr [ebp-20h],eax 

将eax寄存器里面的值赋给[ebp-20h],也就是c,然后输出。

在这里要注意:

栈帧这部分内容在不同的编译器上实现存在差异,但是思想是一致的。

这里我用的是vs2017,如果在vc上面它里面的局部变量是连续存放的,而在VS中,有可能中间还隔了几个地址。

 

我将每个函数栈帧图片放出来,供大家参考:

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值