参数传递顺序

  • 从右到左依次入栈:__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 字节的参数一次性放入寄存器 ecxedx,其余参数再依次压入栈中。