参数传递顺序

  • 从右到左依次入栈:__stdcall、__cdecl、__thiscall、__fastcall
  • 从左到右依次入栈:__pascal

__stdcall 与 __cdecl 的区别

这两种调用约定最大的差别在于谁负责堆栈平衡:

  • __stdcall:由被调用的函数自身负责堆栈平衡。
  • __cdecl:由调用方(调用者)负责该函数的堆栈平衡。

__stdcall:函数本身负责堆栈平衡

下面的例子里,函数 a 使用 __stdcall 调用约定,可以从反汇编中看到 ret 8 由被调用函数自己完成堆栈平衡,这里 8 正好对应两个 int 参数占用的 8 字节。

int  __stdcall a(int v1,int xx)
                {
                		return 5;
                }
                
                int main()
                {
                	int t=a(5,1);
                	return 0;
                }
                
                /**/
                --- c:\users\hekun\desktop\cppp\cppp\源.cpp -------------------------------------
                int  __stdcall a(int v1,int xx)
                {
                001713C0  push        ebp
                001713C1  mov         ebp,esp
                001713C3  sub         esp,0C0h
                001713C9  push        ebx
                001713CA  push        esi
                001713CB  push        edi
                001713CC  lea         edi,[ebp-0C0h]
                001713D2  mov         ecx,30h
                001713D7  mov         eax,0CCCCCCCCh
                001713DC  rep stos    dword ptr es:[edi]
                		return 5;
                001713DE  mov         eax,5
                }
                001713E3  pop         edi
                001713E4  pop         esi
                001713E5  pop         ebx
                001713E6  mov         esp,ebp
                001713E8  pop         ebp
                001713E9  ret         8  /*堆栈平衡 函数参数占用8字节*/
                
                --- c:\users\hekun\desktop\cppp\cppp\源.cpp -------------------------------------
                int main()
                {
                00171400  push        ebp
                00171401  mov         ebp,esp
                00171403  sub         esp,0CCh
                00171409  push        ebx
                0017140A  push        esi
                0017140B  push        edi
                0017140C  lea         edi,[ebp-0CCh]
                00171412  mov         ecx,33h
                00171417  mov         eax,0CCCCCCCCh
                0017141C  rep stos    dword ptr es:[edi]
                	int t=a(5,1);
                0017141E  push        1
                00171420  push        5
                00171422  call        a (01711E0h)  /* 调用函数a*/
                00171427  mov         dword ptr [t],eax  /*返回值位于寄存器eax*/
                
                	return 0;
                0017142A  xor         eax,eax
                }
                0017142C  pop         edi
                0017142D  pop         esi
                0017142E  pop         ebx
                0017142F  add         esp,0CCh
                00171435  cmp         ebp,esp
                00171437  call        __RTC_CheckEsp (017113Bh)
                0017143C  mov         esp,ebp
                0017143E  pop         ebp
                0017143F  ret

__cdecl:调用方负责堆栈平衡

把同一个函数换成 __cdecl,在反汇编里可以看到函数 a 直接 ret,不再负责清理栈;平衡工作转移到了 main 中的 add esp,8。这也正是变参函数(如 printf)必须使用 __cdecl 的原因——只有调用方才知道实际压入了多少字节的参数。

int  __cdecl a(int v1,int xx)
                {
                		return 5;
                }
                
                int main()
                {
                	int t=a(5,1);
                
                	return 0;
                }
                
                
                
                
                /*
                反汇编
                
                */
                
                --- c:\users\hekun\desktop\cppp\cppp\源.cpp -------------------------------------
                
                int  __cdecl a(int v1,int xx)
                {
                00F313C0  push        ebp
                00F313C1  mov         ebp,esp
                00F313C3  sub         esp,0C0h
                00F313C9  push        ebx
                00F313CA  push        esi
                00F313CB  push        edi
                00F313CC  lea         edi,[ebp-0C0h]
                00F313D2  mov         ecx,30h
                00F313D7  mov         eax,0CCCCCCCCh
                00F313DC  rep stos    dword ptr es:[edi]
                		return 5;
                00F313DE  mov         eax,5
                }
                00F313E3  pop         edi
                00F313E4  pop         esi
                00F313E5  pop         ebx
                00F313E6  mov         esp,ebp
                00F313E8  pop         ebp
                00F313E9  ret
                
                --- c:\users\hekun\desktop\cppp\cppp\源.cpp -------------------------------------
                
                int main()
                {
                00F31400  push        ebp
                00F31401  mov         ebp,esp
                00F31403  sub         esp,0CCh
                00F31409  push        ebx
                00F3140A  push        esi
                00F3140B  push        edi
                00F3140C  lea         edi,[ebp-0CCh]
                00F31412  mov         ecx,33h
                00F31417  mov         eax,0CCCCCCCCh
                00F3141C  rep stos    dword ptr es:[edi]
                	int t=a(5,1);
                00F3141E  push        1
                00F31420  push        5
                00F31422  call        a (0F311E5h)  /* 调用函数 a  */
                00F31427  add         esp,8     /* 由于是 __cdecl 调用方式 所以调用者 main 负责堆栈平衡,函数a 返回后 立即堆栈平衡(占用8字节)  变参函数只能是这种调用,因为只有调用者才知道 参数实际大小*/
                00F3142A  mov         dword ptr [t],eax
                
                	return 0;
                00F3142D  xor         eax,eax
                }
                00F3142F  pop         edi
                00F31430  pop         esi
                00F31431  pop         ebx
                00F31432  add         esp,0CCh
                00F31438  cmp         ebp,esp
                00F3143A  call        __RTC_CheckEsp (0F3113Bh)
                00F3143F  mov         esp,ebp
                00F31441  pop         ebp
                00F31442  ret

__fastcall

__fastcall 调用约定会把前面最多 8 字节的参数一次性放入寄存器 ecx 和 edx,其余参数再依次压入栈中。