函数调用规范
调用规范是指进行一次函数调用所采用的传递参数的方法,返回值的处理以及堆栈的清理等等。常见的调用规范有: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
}