函数调用堆栈、返回值与调用约定

在看文章之前来了解两个指针:
ebp:指向函数栈底的指针
esp:指向函数栈顶栈顶指针

#include<stdio.h>

int sum(int a, int b)
{
	 int temp = 0;
	 temp = a + b;
	 return temp;
}
int main()
{
	 int a = 10;
	 int b = 20;
	 int ret = 0;
	 ret = sum(a, b);
	 printf("ret = %d\n",ret);
	 return 0;
}

一、函数堆栈的建立过程
接下我给程序打个断点,看一下执行时的部分汇编代码是什么样的:
1.main函数第一个大括号到函数第一条语句之间的汇编代码:

    17: int main()
    18: {
00151410  push        ebp  
00151411  mov         ebp,esp  
00151413  sub         esp,0E4h  
00151419  push        ebx  
0015141A  push        esi  
0015141B  push        edi  
0015141C  lea         edi,[ebp-0E4h]  
00151422  mov         ecx,39h  
00151427  mov         eax,0CCCCCCCCh  
0015142C  rep stos    dword ptr es:[edi]  
    19:  int a = 10;
##############################################################################
 这一段汇编程序是干什么的?
 main函数也是一个函数,它的执行是被其他函数调用的
 这段汇编代码实际上是给main函数的执行开辟栈帧
 
00151413  sub         esp,0E4h  指定了main函数的栈帧大小  大小为0E4h 

00151427  mov         eax,0CCCCCCCCh  
0015142C  rep stos    dword ptr es:[edi]     
循环的对main函数的栈帧进行初始化,初始值为0CCCCCCCCh 
这就是我们打印没有初始化的变量看到的”烫烫烫“

2.再来看一下汇编层面上,局部变量是怎么被cpu拿到的:

    19:  int a = 10;
0015142E  mov         dword ptr [ebp - 4],0Ah  
    20:  int b = 20;
00151435  mov         dword ptr [ebp - 8],14h  
    21:  int ret = 0;
0015143C  mov         dword ptr [ebp - 0Ch],0  
########################################################
我们可以看到对a、b、ret等局部变量的时候不是在内存中取。
而是通过ebp指针的偏移来取的,不是操作 a b ret 等名字,因为他们都是指令
他们不会产生符号,保存在.text段

3.sum函数被调用时的汇编代码

//这里展示的是压实参的过程
    22:  ret = sum(a, b);
00151443  mov         eax,dword ptr [ebp -8]  
00151446  push        eax  
00151447  mov         ecx,dword ptr [ebp -4]  
0015144A  push        ecx  
0015144B  call        sum (015105Fh)  
00151450  add         esp,8  
00151453  mov         dword ptr [ebp - 0Ch],eax  
#################################################
着重看一下这两行代码:
下面两行汇编之前的汇编代码是分别将形参a b入栈
0015144B  call        sum (015105Fh)  
00151450  add         esp,8  

call指令做了两件事情:
1.跳入sum函数
2.在进入sum函数之前,把call指令的下一行指令的地址入栈,即00151450  add         esp,8  
这样能够保证sum函数执行完以后,cpu知道自己该干嘛,pc寄存器将存储add         esp,8   这一行的地址

4.进入sum函数大括号和第一条指令之间也有一大堆的汇编代码:
这堆汇编是干什么的?

    10: int sum(int a, int b)
    11: {
001513C0  push        ebp  
001513C1  mov         ebp,esp  
001513C3  sub         esp,0CCh  
001513C9  push        ebx  
001513CA  push        esi  
001513CB  push        edi  
001513CC  lea         edi,[ebp-0CCh]  
001513D2  mov         ecx,33h  
001513D7  mov         eax,0CCCCCCCCh  
001513DC  rep stos    dword ptr es:[edi]  
    12:  int temp = 0;
####################################
这里的汇编是给sum函数的执行开辟栈帧,通过移动esp和ebp
001513C3  sub         esp,0CCh    这行代码说明了sum栈帧的大小,移动了0CCH字节

001513D7  mov         eax,0CCCCCCCCh  
001513DC  rep stos    dword ptr es:[edi]  
循环的对main函数的栈帧进行初始化,初始值为0CCCCCCCCh 
这就是我们打印没有初始化的变量看到的”烫烫烫“

扩展:对于没有初始化堆上的内存,我们看到是”屯屯屯…”

5.sum函数的返回值如何带出去的呢:

    14:  return temp;
001513EE  mov         eax,dword ptr [temp] 

由于sum函数类型为int,返回值== 4B,我们可以看到汇编上是通过寄存器将temp带回去的

6.sum函数的栈帧回退时,对使用过的内存进行处理了吗?

    15: }
001513F1  pop         edi  
001513F2  pop         esi  
001513F3  pop         ebx  
001513F4  mov         esp,ebp  
001513F6  pop         ebp  
001513F7  ret  
####################################################
实际上,我们能看到,sum函数在回退栈帧的时候,并没有对这块内存进行清零啊之类的操作
仅仅是esp回退而已
所以,有时我们尝试试图用非正常的去访问无效的内存可以看到有效的值,就不觉得奇怪了。

001513F6  pop         ebp    通过该操作就知道主调函数的栈底地址,因为函数调用前call就把主调函数的栈底地址push进了被调函数的栈帧中。

在这里插入图片描述

二、函数堆栈的回退过程:
在这里插入图片描述

整个过程如上。

二、函数的返回值传递方式:

在函数开辟堆栈时,我们根据函数返回值的类型的大小,判断是否要产生临时量来将返回值带出去
<= 4B:一个寄存器带回
8B=< > =4B:通过两个寄存器带回
.>8B:通过产生临时量的方式处理返回值

大于8个字节时处理返回值的过程:返回值太大,寄存器无法带回去。
整个过程如下:

1.函数调用前根据函数的返回值类型,确定函数返回值的大小
2.提前在主调函数的栈帧上开辟一块内存,然后调用函数的时候将这块内存的地址压栈进去,等到所以的实参入栈以后就将这块内存的地址入栈
3.被调函数产生的数据将根据临时量的内存的地址直接拷贝回主调函数

注意:
只要不是内置类型,只要是自定义的类型,返回值的时候都会产生临时量。

三、函数的调用约定

_cdecl:c调用约定,默认是该约定
_stdcall:windows标准的调用约定
_fastcall:快速调用约定
_thiscall:c++的成员函数的调用约定

1.函数产生的符号名字不同
2.函数参数的入栈顺序不同
3.谁来清理形参的内存

解答:
_cdecl:调用方开辟形参内存,调用方自己清理形参内存
_stdcall:调用方开辟形参内存,被调方自己释放形参内存
_fastcall:调用方开辟形参内存,但是把最后8个字节的实参通过寄存器带到被调用函数,被调方自己释放形参内存

int _cdecl sum(int a, int b)
{
	//...
}
int _fastcall sum(int a, int b)
{
	//...
}
int _stdcall sum(int a, int b)
{
	//...
}

猜你喜欢

转载自blog.csdn.net/KingOfMyHeart/article/details/89280925
今日推荐