您的位置:首页 > 其它

调用约定总结

2008-03-04 21:17 176 查看
转载: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中
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: