函数调用规范

     函数调用规范

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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值