C++ ——函数调用栈 解析

这篇博客,我们来了解一下函数是如何传参?,函数返回值 如何返回?,以及他们的栈帧的开辟。

首先我们先来看一个简单的函数调用。

int fun(int a,int b)
{
    return a+b;
}
int main()
{
    int a = fun(10,20);
    return 0;
}

将汇编代码执行到函数调用的地方查看函数调用参数带入的指令。

int main()
{
00FD1780  push        ebp  
00FD1781  mov         ebp,esp  
00FD1783  sub         esp,0CCh  
00FD1789  push        ebx  
00FD178A  push        esi  
00FD178B  push        edi  
00FD178C  lea         edi,[ebp-0CCh]  
00FD1792  mov         ecx,33h  
00FD1797  mov         eax,0CCCCCCCCh  
00FD179C  rep stos    dword ptr es:[edi]  
	int a = fun(10, 20);
00FD179E  push        14h  //从左向右
00FD17A0  push        0Ah  
00FD17A2  call        fun (0FD1168h)  
00FD17A7  add         esp,8  //向上偏移8个字节
00FD17AA  mov         dword ptr [a],eax  //将a的值放入到eax寄存器中
	return 0;
00FD17AD  xor         eax,eax  
}

如果我们传入的参数大小为一个字节时

struct Tmp
{
    char a;//大小为一个字节
}
int fun1(struct Tmp a,struct Tmp b)
{
    return 0;
}
int main()
{
    struct Tmp tmp1,tmp2;
    tmp1.a = 10;
    tmp2.a = 20;
    int a = fun1(tmp1,tmp2);
    return 0;
}

汇编代码:

int a = fun(tmp1, tmp2);
01053BF6  movzx       eax,byte ptr [tmp2]  //将tmp2的值给eax寄存器
01053BFA  push        eax                  //将eax压入栈中
01053BFB  movzx       ecx,byte ptr [tmp1]  //将tmp1的值给ecx寄存器
01053BFF  push        ecx                  //压栈
01053C00  call        fun (01051370h)  
01053C05  add         esp,8                //esp向上抬升8字节
01053C08  mov         dword ptr [a],eax    //将a的值压入到 eax寄存器中
	return 0;

我们发现它是通过push ---eax和ecx寄存器进行参数的传入。

参数大小为 8 字节

struct Tmp
{
    int a;

    int b;
};//大小为8个字节
int fun1(struct Tmp a,struct Tmp b)
{
    return 0;
}
int main()
{
    struct Tmp tmp1,tmp2;
    tmp1.a = 10;
    tmp1.b = 20;
    tmp2.a = 15
    tmp2.b = 25;
    int a = fun1(tmp1,tmp2);
    return 0;
}

看一下8个字节大小的参数,是如何传参的。仍然是通过寄存器push。

int a = fun(tmp1, tmp2);
000E410A  mov         eax,dword ptr [ebp-18h]  
000E410D  push        eax  
000E410E  mov         ecx,dword ptr [tmp2]  
000E4111  push        ecx  
000E4112  mov         edx,dword ptr [ebp-8]  
000E4115  push        edx  
000E4116  mov         eax,dword ptr [tmp1]  
000E4119  push        eax  
000E411A  call        fun (0E1370h)  
000E411F  add         esp,10h  
000E4122  mov         dword ptr [a],eax  
	return 0;
//仍然是通过寄存器进行传参的。

参数的大小>8字节

struct Tmp
{
    int a;
    int b;
    int c;
};//大小为12个字节,大于8个。

汇编代码:

int a = fun(tmp1, tmp2);
002D4112  sub         esp,0Ch  //现将栈顶寄存器向上抬了 0Ch的大小。esp减12
002D4115  mov         eax,esp  //将esp的值给eax寄存器
002D4117  mov         ecx,dword ptr [tmp2]  //将tmp.a的值写入ecx寄存器
002D411A  mov         dword ptr [eax],ecx   // 将ecx寄存器的值写入到eax寄存器中
002D411C  mov         edx,dword ptr [ebp-20h]  //将tmp.b的值写入edx寄存器中
002D411F  mov         dword ptr [eax+4],edx    //将edx寄存器的值写入eax寄存器中
002D4122  mov         ecx,dword ptr [ebp-1Ch]  //将tmp.c的值写入ecx寄存器中
002D4125  mov         dword ptr [eax+8],ecx    //将ecx寄存器中的值写入eax寄存器中
002D4128  sub         esp,0Ch  
002D412B  mov         edx,esp  
002D412D  mov         eax,dword ptr [tmp1]  
002D4130  mov         dword ptr [edx],eax  
002D4132  mov         ecx,dword ptr [ebp-0Ch]  
002D4135  mov         dword ptr [edx+4],ecx  
002D4138  mov         eax,dword ptr [ebp-8]  
002D413B  mov         dword ptr [edx+8],eax  
002D413E  call        fun (02D1370h)  
002D4143  add         esp,18h  
002D4146  mov         dword ptr [a],eax  
	return 0;

  从上面的汇编代码可以看出,在函数参数为12字节的时候,其参数带入方式和小于以及等于8字节的时候不同,这里没有明显的push函数,而是现在main函数的栈顶向上移动12字节,然后将参数的数据拷贝到main函数栈顶开辟的内存中。其方式如图3

二:函数返回值返回

  1.对于小于等于8字节的参数:其返回至是利用寄存器带回,然后将寄存器的值写入到接受返回值的变量中。

  2.从上面的汇编代码中来看,当函数的返回值为12个字节的时间,还在参数入栈的最后入栈了一个寄存器该寄存器中储存main函数上的一块内存。

  而在返回值的时候,先从ebp - 8位置取值,取出的正好是参数入栈之后入栈的一块main函数的地址,然后将返回的数据写入到该块内存上。

  由此可见,当返回值大于8字节的时候是预先在调用方的栈帧上预留一块内存,作为函数返回值存储的位置,最后返回值的时候,将返回值的数据写入到该段内存。大致如图6

函数栈帧的回退

函数栈帧的回退分为两步,一步是函数栈帧的回退,另一步是函数参数的清楚。

三种调用的约定

①_cdecl

C/C++默认的调用约定,其调用方式如上面所示

②_stdcall

该调用约定和_cdecl相似,唯一区别在于,函数参数的清除是由被调用方直接完成,而非调用方完成。

③_fastcall

 是快速调用约定,在参数入栈的时候,最后入栈的独立四字节或者8字节利用寄存器带入,其他的参数在当前栈顶开辟空间写入,寄存器带入的参数在被调用方栈帧开辟之后直接写入到其栈帧上。

栈帧回退清除函数的时候,在被调用方栈帧上的参数不用清楚,会跟随栈帧回退被清除,其他参数由被调用方自己清除

猜你喜欢

转载自blog.csdn.net/genzld/article/details/83794245