函数调用规范
调用规范是指进行一次函数调用所采用的传递参数的方法,返回值的处理以及堆栈的清理等等。常见的调用规范有:stdcall、cdecl、fastcall、thiscall、nakedcall
一、stdcall调用规范
stdcall很多时候被称为pascal调用规范,因为pascal是早期很常见的一种教学用计算机程序设计语言,其语法严谨,使用的函数调用约定是stdcall。在Microsoft C++系列的C/C++编译器中,常常用PASCAL宏来声明这个调用约定,类似的宏还有WINAPI和CALLBACK。 stdcall调用规范声明的语法为:int __stdcall fun( int a, int b );stdcall的调用约定:
(1) 参数从右向左压入堆栈。
(2) 函数自身调整堆栈。
(3) 返回函数调用结果。
stdcall的其它说明:
由于函数自身调整堆栈,调整堆栈代码包含再被调函数中,所以最后生成的代码文件相对较小;由于函数自身调整堆栈,必须预先知道函数参数个数,所以参数个数不能是可变的;在c和c++中输出时函数名都会被修饰,通常是再函数名前加下划线,后面紧跟“@”符号,其后紧跟参数的尺寸。可以通过.def文件去除函数名修饰;
stdcall实例分析:
int __stdcall fun( int, int ); int main() { int nRet = fun ( 1, 2 ); printf( "%d\n", nRet ); return 0; } int __stdcall fun( int a, int b ) { return a + b; }
编译成汇编代码如下:
8: int main() 9: { 00401020 push ebp // 系统代码 00401021 mov ebp,esp 00401023 sub esp,44h 00401026 push ebx 00401027 push esi 00401028 push edi 00401029 lea edi,[ebp-44h] 0040102C mov ecx,11h 00401031 mov eax,0CCCCCCCCh 00401036 rep stos dword ptr [edi] 10: int nRet = Fun( 1 , 2 ); 00401038 push 2 // 将第2个参数压入栈中 0040103A push 1 // 将第1个参数压入栈中 0040103C call @ILT+0(Fun) (00401005) // 跳到Fun函数的入口 // 00401005 jmp Fun (00401080) 00401041 mov dword ptr [ebp-4],eax // 保存返回值 11: printf( "%d\n", nRet ); 00401044 mov eax,dword ptr [ebp-4] 00401047 push eax 00401048 push offset string "%d\n" (0042201c) 0040104D call printf (004010b0) 00401052 add esp,8 12: return 0; 00401055 xor eax,eax 13: } // 省略其它汇编代码 ...... 15: int Fun( int a, int b ) 16: { 00401080 push ebp // 将当前栈顶地址压入栈中,用于函数退出时回复堆栈指针 00401081 mov ebp,esp 00401083 sub esp,40h 00401086 push ebx 00401087 push esi 00401088 push edi 00401089 lea edi,[ebp-40h] 0040108C mov ecx,10h 00401091 mov eax,0CCCCCCCCh 00401096 rep stos dword ptr [edi] 17: return a + b; 00401098 mov eax,dword ptr [ebp+8] 0040109B add eax,dword ptr [ebp+0Ch] 18: } 0040109E pop edi 0040109F pop esi 004010A0 pop ebx 004010A1 mov esp,ebp 004010A3 pop ebp 004010A4 ret 8 // 返回并恢复堆栈,两个int参数大小为8
二、cdecl调用规范
cdecl调用约定又称为C调用约定,是C语言缺省的调用约定。在c中函数不加调用规范修饰符时默认为此调用规范。cdecl调用规范声明的语法为:int __cdecl fun( int a, int b );cdecl的调用约定:
(1) 参数从右向左压入堆栈。
(2) 返回函数调用结果。
(3) 函数调用者调整堆栈。
cdecl的其它说明:
由于函数调用者需要调整堆栈,每次调用都要生成调整堆栈代码,所以最后生成的代码文件相对要大一些;由于函数调用者调整堆栈,调用者知道函数参数个数并在函数运行完后清理,所以函数参数个数可变;在c中输出时函数名不会被修饰。在c++中输出时函数名会被修饰,可以通过在前面添加extern "C"或.def文件去掉函数名修饰;
cdecl实例分析:
i
nt fun( int, int ); int main() { int nRet = fun ( 1, 2 ); printf( "%d\n", nRet ); return 0; } int fun( int a, int b ) { return a + b; }
编译成汇编代码如下:
8: int main() 9: { 00401020 push ebp // 系统代码 00401021 mov ebp,esp 00401023 sub esp,44h 00401026 push ebx 00401027 push esi 00401028 push edi 00401029 lea edi,[ebp-44h] 0040102C mov ecx,11h 00401031 mov eax,0CCCCCCCCh 00401036 rep stos dword ptr [edi] 10: int nRet = Fun( 1 , 2 ); 00401038 push 2 // 将第2个参数压入栈中 0040103A push 1 // 将第1个参数压入栈中 0040103C call @ILT+0(Fun) (00401005) // 跳到Fun函数的入口 // 00401005 jmp Fun (00401080) 00401041 add esp,8 // 恢复堆栈,两个int参数大小为8 00401044 mov dword ptr [ebp-4],eax // 保存返回值 11: printf( "%d\n", nRet ); 00401047 mov eax,dword ptr [ebp-4] 0040104A push eax 0040104B push offset string "%d\n" (0042201c) 00401050 call printf (004010b0) 00401055 add esp,8 12: return 0; 00401058 xor eax,eax 13: } // 省略其它汇编代码 ...... 15: int Fun( int a, int b ) 16: { 00401080 push ebp // 00401081 mov ebp,esp 00401083 sub esp,40h 00401086 push ebx 00401087 push esi 00401088 push edi 00401089 lea edi,[ebp-40h] 0040108C mov ecx,10h 00401091 mov eax,0CCCCCCCCh 00401096 rep stos dword ptr [edi] 17: return a + b; 00401098 mov eax,dword ptr [ebp+8] 0040109B add eax,dword ptr [ebp+0Ch] 18: } 0040109E pop edi 0040109F pop esi 004010A0 pop ebx 004010A1 mov esp,ebp 004010A3 pop ebp 004010A4 ret
三、fastcall调用规范
fastcall调用约定和stdcall类似。fastcall调用规范声明的语法为:int __fastcall fun( int a, int b );fastcall的调用约定:
(1) 函数的第一个和第二个DWORD参数(或者尺寸更小的)通过ecx和edx传递,其它参数从右向左压入堆栈。
(2) 函数自身调整堆栈。
(3) 返回函数调用结果。
fastcall的其它说明:
与stdcall雷同;
fastcall实例分析:
int __fastcall fun( int, int, int, int ); int main() { int nRet = fun ( 1, 2, 3, 4 ); printf( "%d\n", nRet ); return 0; } int __fastcall fun( int a, int b, int c, int d ) { return a + b + c + d; }
编译成汇编代码如下:
8: int main() 9: { 00401020 push ebp // 系统代码 00401021 mov ebp,esp 00401023 sub esp,44h 00401026 push ebx 00401027 push esi 00401028 push edi 00401029 lea edi,[ebp-44h] 0040102C mov ecx,11h 00401031 mov eax,0CCCCCCCCh 00401036 rep stos dword ptr [edi] 10: int nRet = Fun( 1, 2, 3, 4 ); 00401038 push 4 // 将第4个参数压入栈中 0040103A push 3 // 将第3个参数压入栈中 0040103C mov edx,2 // 将第2个参数存入edx中 00401041 mov ecx,1 // 将第1个参数存入ecx中 00401046 call @ILT+0(Fun) (00401005) // 跳到Fun函数的入口 // 00401005 jmp Fun (00401090) 0040104B mov dword ptr [ebp-4],eax 11: printf( "%d\n", nRet ); 0040104E mov eax,dword ptr [ebp-4] 00401051 push eax 00401052 push offset string "%d\n" (0042201c) 00401057 call printf (004010e0) 0040105C add esp,8 12: return 0; 0040105F xor eax,eax 13: } // 省略其它汇编代码 ...... 15: int Fun( int a, int b, int c, int d ) 16: { 00401090 push ebp // 00401091 mov ebp,esp 00401093 sub esp,48h 00401096 push ebx 00401097 push esi 00401098 push edi 00401099 push ecx 0040109A lea edi,[ebp-48h] 0040109D mov ecx,12h 004010A2 mov eax,0CCCCCCCCh 004010A7 rep stos dword ptr [edi] 004010A9 pop ecx 004010AA mov dword ptr [ebp-8],edx 004010AD mov dword ptr [ebp-4],ecx 17: return a + b + c + d; 004010B0 mov eax,dword ptr [ebp-4] 004010B3 add eax,dword ptr [ebp-8] 004010B6 add eax,dword ptr [ebp+8] 004010B9 add eax,dword ptr [ebp+0Ch] 18: } 004010BC pop edi 004010BD pop esi 004010BE pop ebx 004010BF mov esp,ebp 004010C1 pop ebp 004010C2 ret 8 // 返回并恢复堆栈,第3和第4个参数大小为8
四、thiscall调用规范
thiscall是唯一一个不能明确指明的函数修饰,因为thiscall不是关键字。它是C++类成员函数缺省的调用约定。由于成员函数调用还有一个this指针,因此必须特殊处理。thiscall调用规范声明的语法为:不需要明确修饰,在类声明中自动采用改调用规范;thiscall的调用约定:
(1) 参数从右向左压入堆栈。如果参数个数确定,this指针ü齟cx传递给被调用者;如果参数个数不确定,this指针在所有参数入栈后被压入堆栈。
(2) 如果参数个数确定,函数自身调整堆栈再返回函数调用结果。
(3) 如果参数个数不确定,则先返回函数调用结果并由函数调用者调整堆栈。
thiscall的其它说明:
一种特别的调用规范,函数参数个数确定的情况下类似于stdcall,函数参数个数不确定的情况下类似于cdecl。
thiscall实例分析:
class CFun { public: int Fun1( int, int ); int Fun2( int, ... ); }; int main() { CFun c; int nRet1 = c.Fun1( 1, 2 ); int nRet2 = c.Fun2( 3, 1, 2, 3 ); printf( "%d\n%d\n", nRet1, nRet2 ); return 0; } int CFun::Fun1( int a, int b ) { return a + b; } int CFun::Fun2( int a, ... ) { va_list plst; va_start( plst, a ); int nRet = 0; for ( int i = 0; i < a; i++ ) { nRet += va_arg( plst, int ); } return nRet; }
编译成汇编代码如下:
17: int main() 18: { 00401030 push ebp 00401031 mov ebp,esp 00401033 sub esp,4Ch 00401036 push ebx 00401037 push esi 00401038 push edi 00401039 lea edi,[ebp-4Ch] 0040103C mov ecx,13h 00401041 mov eax,0CCCCCCCCh 00401046 rep stos dword ptr [edi] 19: CFun c; 20: int nRet1 = c.Fun1( 1, 2 ); 00401048 push 2 0040104A push 1 0040104C lea ecx,[ebp-4] 0040104F call @ILT+10(CFun::Fun1) (0040100f) 00401054 mov dword ptr [ebp-8],eax 21: int nRet2 = c.Fun2( 3, 1, 2, 3 ); 00401057 push 3 00401059 push 2 0040105B push 1 0040105D push 3 0040105F lea eax,[ebp-4] 00401062 push eax 00401063 call @ILT+0(CFun::Fun2) (00401005) 00401068 add esp,14h 0040106B mov dword ptr [ebp-0Ch],eax 22: printf( "%d\n%d\n", nRet1, nRet2 ); 0040106E mov ecx,dword ptr [ebp-0Ch] 00401071 push ecx 00401072 mov edx,dword ptr [ebp-8] 00401075 push edx 00401076 push offset string "%d\n%d\n" (0042201c) 0040107B call printf (00401170) 00401080 add esp,0Ch 23: return 0; 00401083 xor eax,eax 24: } // 省略其它汇编代码 ...... 26: int CFun::Fun1( int a, int b ) 27: { 004010B0 push ebp 004010B1 mov ebp,esp 004010B3 sub esp,44h 004010B6 push ebx 004010B7 push esi 004010B8 push edi 004010B9 push ecx 004010BA lea edi,[ebp-44h] 004010BD mov ecx,11h 004010C2 mov eax,0CCCCCCCCh 004010C7 rep stos dword ptr [edi] 004010C9 pop ecx 004010CA mov dword ptr [ebp-4],ecx 28: return a + b; 004010CD mov eax,dword ptr [ebp+8] 004010D0 add eax,dword ptr [ebp+0Ch] 29: } 004010D3 pop edi 004010D4 pop esi 004010D5 pop ebx 004010D6 mov esp,ebp 004010D8 pop ebp 004010D9 ret 8 31: int CFun::Fun2( int a, ... ) 32: { 004010F0 push ebp 004010F1 mov ebp,esp 004010F3 sub esp,4Ch 004010F6 push ebx 004010F7 push esi 004010F8 push edi 004010F9 lea edi,[ebp-4Ch] 004010FC mov ecx,13h 00401101 mov eax,0CCCCCCCCh 00401106 rep stos dword ptr [edi] 33: va_list plst; 34: va_start( plst, a ); 00401108 lea eax,[ebp+10h] 0040110B mov dword ptr [ebp-4],eax 35: int nRet = 0; 0040110E mov dword ptr [ebp-8],0 36: for ( int i = 0; i < a; i++ ) 00401115 mov dword ptr [ebp-0Ch],0 0040111C jmp CFun::Fun2+37h (00401127) 0040111E mov ecx,dword ptr [ebp-0Ch] 00401121 add ecx,1 00401124 mov dword ptr [ebp-0Ch],ecx 00401127 mov edx,dword ptr [ebp-0Ch] 0040112A cmp edx,dword ptr [ebp+0Ch] 0040112D jge CFun::Fun2+56h (00401146) 37: { 38: nRet += va_arg( plst, int ); 0040112F mov eax,dword ptr [ebp-4] 00401132 add eax,4 00401135 mov dword ptr [ebp-4],eax 00401138 mov ecx,dword ptr [ebp-4] 0040113B mov edx,dword ptr [ebp-8] 0040113E add edx,dword ptr [ecx-4] 00401141 mov dword ptr [ebp-8],edx 39: } 00401144 jmp CFun::Fun2+2Eh (0040111e) 40: return nRet; 00401146 mov eax,dword ptr [ebp-8] 41: } 00401149 pop edi 0040114A pop esi 0040114B pop ebx 0040114C mov esp,ebp 0040114E pop ebp 0040114F ret
五、naked call调用规范
naked call这是一个很少见的调用约定,一般程序设计者建议不要使用。编译器不会给这种函数增加初始化和清理代码,更特殊的是,不能用return返回返回值,只能用插入汇编返回结果。这一般用于实模式驱动程序设计。这个修饰是和stdcall及cdecl结合使用的。naked call调用规范声明的语法为:
__declspec(naked) int fun( int a, int b); // 默认为cdecl
__declspec(naked) int __stdcall fun( int a, int b );
naked call的调用约定:
(1) 视函数前面的修饰符而定。
naked call的其它说明:
一种特别的调用规范,在函数中使用汇编代码,如下:
__declspec(naked) int __stdcall fun( int a, int b ) { __asm mov eax,a __asm add eax,b __asm ret 8 // 注意值8 }