参数传递顺序
- 从右到左依次入栈:
__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,其余参数再依次压入栈中。