调用约定总结

转载:http://blog.csdn.net/turkeycock/archive/2008/03/02/2138789.aspx

作者:钱鑫

调用约定(Calling convention)决定了以下内容:
1)参数的压栈顺序(自右向左还是自左向右)
2)函数返回时,由调用函数还是被调用函数清理入栈的参数
3)编译时函数名的转换

一共有五种调用约定,下面将一一详细介绍。

1.__stdcall
参数自右向左压栈
被调用函数在返回前清理入栈参数
C编译时函数名的转换:_function@number
其中function为函数名,number为参数的字节数
例:int MyFunc(int a, int b)
    _MyFucn@8
C++编译时函数名的转换:?function@@YG****@Z或者?function@@YG*XZ
若函数有参数,以@Z结束;若函数无参数,则以Z结束
其中function为函数名,*代表参数表,为下列值:
      X--void ,
      D--char,
      E--unsigned char,
      F--short,
      H--int,
      I--unsigned int,
      J--long,
      K--unsigned long,
      M--float,
      N--double,
      _N--bool,
      PA--表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以"0"代替,一个"0"代表一次重复
参数表第一项为返回类型,其后跟参数的类型,指针标识在其所指数据类型前
例:int MyFunc1(unsigned char *arg1, unsigned long arg2)
    ?MyFunc1@@YGHPAEK@Z
    void MyFunc2(char *arg1, char *arg2, char *arg3)
    ?MyFunc2@@YGXPAD00@Z
    void MyFunc3()
   
?MyFunc3@@YGXXZ
C++编译器转换函数名时更多的考虑了参数,主要是为了方便函数重载,而C语言则不存在函数重载问题

2.__cdecl
参数自右向左压栈
调用函数在函数返回后清理入栈参数
C编译时函数名的转换:_function
其中function为函数名
例:int MyFunc(int a, int b)
    _MyFucn
C++编译时函数名的转换:同__stdcall,把YG改为YA
注意:对于可变参数的成员函数,始终使用__cdecl的转换方式

3.__fastcall
使用ECX传递第一个参数,EDX传递第二个参数,其余参数自右向左压栈
被调用函数在返回前清理入栈参数
C编译时函数名的转换:@function@number
其中function为函数名,number为参数的字节数
例:int MyFunc(int a, int b)
    @MyFucn@8
C++编译时函数名的转换:同__stdcall,把YG改为YI

调用约定可以在Project->Setting...->C/C++->Code Generation中的Calling convention中进行设置,缺省状态为__cdecl

4.thiscall
thiscall不是一个关键字,因此不能在程序中明确指定,它是C++类成员函数缺省的调用约定。由于成员函数调用涉及到一个this指针,因此必须进行特殊处理。
参数自右向左压栈
如果参数个数确定,this指针通过ECX传递给被调用者;如果参数个数不定,this指针在所有参数压栈后被压入堆栈
如果参数个数确定,被调用函数自己清理堆栈;如果参数个数不定,由调用函数清理堆栈
可见,对于参数个数固定情况下,它类似于__stdcall,不定时则类似__cdecl

5.naked call
使用前四种调用约定时,在进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。naked call不产生这样的代码。更特殊的是,不能用return返回返回值,只能用插入汇编返回结果。
naked call必须和__declspec连用,即__declspec(naked),naked call还可以和其他调用约定联用,如:
    __declspec(naked) int __stdcall function(int a, int b)

使用场合:
1、_beginthread需要__cdecl的线程函数地址,_beginthreadex和CreateThread需要__stdcall的线程函数地址。
2、main函数必须是__cdecl,一般的WIN32函数和回调函数都是__stdcall,并且专门定义了宏来标识:
   #define CALLBACK __stdcall
   #define WINAPI  __stdcall

3.如果某函数在C语言编译器中编译,而在C++文件使用,由于两种编译器对函数名的解析不一样,因此需要在C++文件中使用extern "C"进行声明,否则会发生链接错误:
#ifdef _cplusplus
extern "C"{
#endif
int func(int a, int b);
#ifdef _cplusplus
}
#endif

 

 

 

经过上一篇的介绍,大家应该对调用约定有了一定的感性认识。下面通过几个实例加深一下对这五种调用约定的理解。不会查看汇编代码?很简单,在Debug模式下按Ctrl+F11就能看到汇编代码了~
1.__stdcall
源程序:
    int __stdcall add(int a, int b)
    {
        return a + b;
    }
    void main()
    {
        add(10, 20);
    }

汇编代码:
6:        void main()
7:        {
00401050   push        ebp
00401051   mov         ebp,esp
00401053   sub         esp,40h
00401056   push        ebx
00401057   push        esi
00401058   push        edi
00401059   lea         edi,[ebp-40h]
0040105C   mov         ecx,10h
00401061   mov         eax,0CCCCCCCCh
00401066   rep stos    dword ptr [edi]
8:            add(10, 20);
00401068   push        14h
0040106A   push        0Ah
0040106C   call        @ILT+0(_add@8) (00401005)
9:        }
00401071   pop         edi
00401072   pop         esi
00401073   pop         ebx
00401074   add         esp,40h
00401077   cmp         ebp,esp
00401079   call        __chkesp (00401090)
0040107E   mov         esp,ebp
00401080   pop         ebp
00401081   ret


1:        int __stdcall add(int a, int b)
2:        {
00401020   push        ebp
00401021   mov         ebp,esp
00401023   sub         esp,40h
00401026   push        ebx
00401027   push        esi
00401028   push        edi
00401029   lea         edi,[ebp-40h]
0040102C   mov         ecx,10h
00401031   mov         eax,0CCCCCCCCh
00401036   rep stos    dword ptr [edi]
3:            return a + b;
00401038   mov         eax,dword ptr [ebp+8]
0040103B   add         eax,dword ptr [ebp+0Ch]
4:        }
0040103E   pop         edi
0040103F   pop         esi
00401040   pop         ebx
00401041   mov         esp,ebp
00401043   pop         ebp
00401044  
ret         8
注意汇编代码中红色的部分:参数自右向左入栈,函数名为_add@8,由被调用函数清理入栈参数

2.__cdecl
源程序:
    int __cdecl add(int a, int b)
    {
        return a + b;
    }
    void main()
    {
        add(10, 20);
    }

汇编代码:
6:        void main()
7:        {
00401060   push        ebp
00401061   mov         ebp,esp
00401063   sub         esp,40h
00401066   push        ebx
00401067   push        esi
00401068   push        edi
00401069   lea         edi,[ebp-40h]
0040106C   mov         ecx,10h
00401071   mov         eax,0CCCCCCCCh
00401076   rep stos    dword ptr [edi]
8:            add(10, 20);
00401078   push        14h
0040107A   push        0Ah
0040107C   call        @ILT+15(_add) (00401014)
00401081   add         esp,8
9:        }
00401084   pop         edi
00401085   pop         esi
00401086   pop         ebx
00401087   add         esp,40h
0040108A   cmp         ebp,esp
0040108C   call        __chkesp (004010b0)
00401091   mov         esp,ebp
00401093   pop         ebp
00401094   ret


1:        int __cdecl add(int a, int b)
2:        {
00401020   push        ebp
00401021   mov         ebp,esp
00401023   sub         esp,40h
00401026   push        ebx
00401027   push        esi
00401028   push        edi
00401029   lea         edi,[ebp-40h]
0040102C   mov         ecx,10h
00401031   mov         eax,0CCCCCCCCh
00401036   rep stos    dword ptr [edi]
3:            return a + b;
00401038   mov         eax,dword ptr [ebp+8]
0040103B   add         eax,dword ptr [ebp+0Ch]
4:        }
0040103E   pop         edi
0040103F   pop         esi
00401040   pop         ebx
00401041   mov         esp,ebp
00401043   pop         ebp
00401044   ret
注意汇编代码中红色的部分:参数自右向左入栈,函数名为
_add
,被调用函数不清理入栈参数,用ret直接返回;调用函数给esp加8清理入栈参数

3.__fastcall
源程序:
    int __fastcall add(int a, int b)
    {
        return a + b;
    }
    void main()
    {
        add(10, 20);
    }

汇编代码:
6:        void main()
7:        {
00401060   push        ebp
00401061   mov         ebp,esp
00401063   sub         esp,40h
00401066   push        ebx
00401067   push        esi
00401068   push        edi
00401069   lea         edi,[ebp-40h]
0040106C   mov         ecx,10h
00401071   mov         eax,0CCCCCCCCh
00401076   rep stos    dword ptr [edi]
8:            add(10, 20);
00401078   mov         edx,14h
0040107D   mov         ecx,0Ah
00401082   call        @ILT+5(@add@8) (0040100a)
9:        }
00401087   pop         edi
00401088   pop         esi
00401089   pop         ebx
0040108A   add         esp,40h
0040108D   cmp         ebp,esp
0040108F   call        __chkesp (004010b0)
00401094   mov         esp,ebp
00401096   pop         ebp
00401097   ret


1:        int __fastcall add(int a, int b)
2:        {
00401020   push        ebp
00401021   mov         ebp,esp
00401023   sub         esp,48h
00401026   push        ebx
00401027   push        esi
00401028   push        edi
00401029   push        ecx
0040102A   lea         edi,[ebp-48h]
0040102D   mov         ecx,12h
00401032   mov         eax,0CCCCCCCCh
00401037   rep stos    dword ptr [edi]
00401039   pop         ecx
0040103A   mov         dword ptr [ebp-8],edx
0040103D   mov         dword ptr [ebp-4],ecx
3:            return a + b;
00401040   mov         eax,dword ptr [ebp-4]
00401043   add         eax,dword ptr [ebp-8]
4:        }
00401046   pop         edi
00401047   pop         esi
00401048   pop         ebx
00401049   mov         esp,ebp
0040104B   pop         ebp
0040104C  
ret
注意汇编代码中红色的部分:前两个参数分别送往edx和ecx,在函数中函数名为@add@8,由于参数只有两个,不涉及入栈的问题,因此不需要清理栈,直接返回

 

4.thiscall
源程序:
#include <stdarg.h>

class A
{
    public:
      int add(int a, int b);
      int addMore(int a, ...);
};

int A::add(int a, int b)//固定参数
{
    return a + b;
}

int A::addMore(int cnt, ...)//可变参数
{
     va_list a;
     int i;
    int result = 0;
    va_start(a, cnt);
    for(i=0; i<cnt; i++)
    {
         result += va_arg(a, int);
    }
      return result;
}

void main()
{
     A *a = new A();
    a->add(1, 2);
    a->addMore(4, 1, 2, 3, 4);
}

汇编代码:(只看main函数)
31:   void main()
32:   {
004010A0   push        ebp
004010A1   mov         ebp,esp
004010A3   sub         esp,48h
004010A6   push        ebx
004010A7   push        esi
004010A8   push        edi
004010A9   lea         edi,[ebp-48h]
004010AC   mov         ecx,12h
004010B1   mov         eax,0CCCCCCCCh
004010B6   rep stos    dword ptr [edi]
33:       A *a = new A();
004010B8   push        1
004010BA   call        operator new (00401100)
004010BF   add         esp,4
004010C2   mov         dword ptr [ebp-8],eax
004010C5   mov         eax,dword ptr [ebp-8]
004010C8   mov         dword ptr [ebp-4],eax
34:       a->add(1, 2);
004010CB   push        2
004010CD   push        1
004010CF   mov         ecx,dword ptr [ebp-4]
004010D2   call        @ILT+10(A::add) (0040100f)
35:       a->addMore(4, 1, 2, 3, 4);
004010D7   push        4
004010D9   push        3
004010DB   push        2
004010DD   push        1
004010DF   push        4
004010E1   mov         ecx,dword ptr [ebp-4]
004010E4   push        ecx
004010E5   call        @ILT+0(A::addMore) (00401005)
004010EA   add         esp,18h
36:   }
004010ED   pop         edi
004010EE   pop         esi
004010EF   pop         ebx
004010F0   add         esp,48h
004010F3   cmp         ebp,esp
004010F5   call        __chkesp (00401120)
004010FA   mov         esp,ebp
004010FC   pop         ebp
004010FD   ret

注意汇编代码中红色的部分:对于参数固定的函数,this指针放入ecx,由被调用函数清理入栈参数;对于参数不定的函数,this指针在所有参数入栈后压入堆栈,由调用者自己清理入栈参数

5.naked call
    __declspec(naked) int add(int a, int b)
    {
        __asm mov eax, a
         __asm add eax, b
         __asm ret
    }

    void main()
    {
        add(10, 20);
    }

汇编代码:
8:        void main()
9:        {
00401030   push        ebp
00401031   mov         ebp,esp
00401033   sub         esp,40h
00401036   push        ebx
00401037   push        esi
00401038   push        edi
00401039   lea         edi,[ebp-40h]
0040103C   mov         ecx,10h
00401041   mov         eax,0CCCCCCCCh
00401046   rep stos    dword ptr [edi]
10:           add(10, 20);
00401048   push        14h
0040104A   push        0Ah
0040104C   call        @ILT+0(_add) (00401005)
00401051   add         esp,8
11:       }
00401054   pop         edi
00401055   pop         esi
00401056   pop         ebx
00401057   add         esp,40h
0040105A   cmp         ebp,esp
0040105C   call        __chkesp (00401080)
00401061   mov         esp,ebp
00401063   pop         ebp
00401064   ret

1:        __declspec(naked) int add(int a, int b)
2:        {
00401020   mov         eax,dword ptr [ebp+8]
3:            __asm mov eax, a
4:            __asm add eax, b
00401023   add         eax,dword ptr [ebp+0Ch]
5:            __asm ret
00401026  
ret
注意汇编代码中红色的部分:不产生对寄存器的保护代码,不能用return语句返回返回值,只能插入ret汇编语句返回,返回值存放在eax中

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值