函数调用约定就是描述参数如何传递,堆栈由调用方还是被调用方平衡,返回值如何返回等规则。
函数调用约定的几种类型有:__stdcall, __cdecl, __fastcall, __thiscall, __nakedcall, __pascal
下面介绍几种常见的函数调用约定(以VS2010编译器为例):
(1) __cdecl调用约定
1. 参数从右向左传递,放在栈中
2. 栈平衡由调用函数来执行
3. 不定参数的函数可以使用
下面看一个汇编的例子
- int a = 1, b = 2;
- mov dword ptr [a],1
- mov dword ptr [b],2
- int sum = Sum(a, b);
- mov eax,dword ptr [b] // 参数从右向左压入栈,压入参数b
- push eax
- mov ecx,dword ptr [a] // 参数从右向左压入栈,压入参数a
- push ecx
- call Sum (13611A9h) // 调用函数
- add esp,8 // 调用方平衡堆栈(弹出参数)
- mov dword ptr [sum],eax // 返回值保存在eax中
- int __cdecl Sum(int a, int b)
- {
- push ebp // 保存上一层函数栈底指针
- mov ebp,esp // 设置本层函数栈底指针
- sub esp,0C0h // 设置本层函数栈顶指针
- push ebx // 保存寄存器的值:ebx、esi、edi
- push esi
- push edi
- lea edi,[ebp-0C0h] // 栈内容赋初值(调试代码中使用)
- mov ecx,30h
- mov eax,0CCCCCCCCh
- rep stos dword ptr es:[edi]
- return a+b;
- mov eax,dword ptr [a]
- add eax,dword ptr [b] // 将返回值保存在eax中
- }
- pop edi // 恢复寄存器的值:edi、esi、ebx(相反的顺序弹出)
- pop esi
- pop ebx
- mov esp,ebp // 恢复上层函数栈顶指针
- pop ebp // 恢复上层函数栈底指针
- ret // 没有栈平衡操作
__cdecl是c/c++默认的调用方式。从上面的汇编代码可以看到,__cdecl调用方式在函数内没有任何平衡参数的操作,而在退出函数后对esp执行了加8(add esp,8)的操作。C语言中经常使用的printf函数就是典型的__cdecl调用方式,由于printf的参数可以有多个,所以只能以__cdecl方式调用。
(2)__stdcall调用约定
1. 参数从右向左传递,放在栈中
2. 栈平衡操作由被调用函数执行
3. 不定参数的函数无法使用
看一下汇编的例子
- int a = 1, b = 2;
- mov dword ptr [a],1
- mov dword ptr [b],2
- int sum = Sum(a, b);
- mov eax,dword ptr [b] // 参数从右向左压入栈,压入参数b
- push eax
- mov ecx,dword ptr [a] // 参数从右向左压入栈,压入参数a
- push ecx
- call Sum (13611C2h)
- mov dword ptr [sum],eax // 没有平衡栈操作,平衡操作由函数Sum内部完成
- int __stdcall Sum(int a, int b)
- {
- push ebp
- mov ebp,esp
- sub esp,0C0h
- push ebx
- push esi
- push edi
- lea edi,[ebp-0C0h]
- mov ecx,30h
- mov eax,0CCCCCCCCh
- rep stos dword ptr es:[edi]
- return a+b;
- mov eax,dword ptr [a]
- add eax,dword ptr [b]
- }
- pop edi
- pop esi
- pop ebx
- mov esp,ebp
- pop ebp
- ret 8 // 平衡栈操作,栈弹出8个字节,等价于esp += 8
(3)__fastcall调用约定
1. 最左边的两个不大于4字节的参数分别放在ecx和edx寄存器,其余参数仍然从右到左压入栈
2. 被调用方平衡栈
3. 不定参数无法使用
例子:
- int a = 1, b = 2, c = 3;
- mov dword ptr [a],1
- mov dword ptr [b],2
- mov dword ptr [c],3
- int sum = Sum(a, b, c);
- mov eax,dword ptr [c]
- push eax
- mov edx,dword ptr [b] // 最左边的第二个参数由edx传递
- mov ecx,dword ptr [a] // 最左边的第一个参数由ecx传递
- call Sum (0E611CCh)
- mov dword ptr [sum],eax // 没有平衡栈操作,平衡栈操作由被调用方完成
- int __fastcall Sum(int a, int b, int c)
- {
- push ebp
- mov ebp,esp
- sub esp,0D8h
- push ebx
- push esi
- push edi
- push ecx
- lea edi,[ebp-0D8h]
- mov ecx,36h
- mov eax,0CCCCCCCCh
- rep stos dword ptr es:[edi]
- pop ecx
- mov dword ptr [ebp-14h],edx
- mov dword ptr [ebp-8],ecx
- return a+b+c;
- mov eax,dword ptr [a]
- add eax,dword ptr [b]
- add eax,dword ptr [c]
- }
- pop edi
- pop esi
- pop ebx
- mov esp,ebp
- pop ebp
- ret 4 // 平衡栈,4个字节,因为前两个参数是通过寄存器传递的,只有第三个参数压入栈中了
但是,对于浮点值、远指针和__int64类型总是通过栈来传递。
(4) __thiscall调用约定
thiscall仅仅用于c++成员函数。this指针存放于ecx寄存器中,参数从右到左压栈,被调用方平衡栈。thiscall不是关键词不能被程序员指定。
例子:
- int a = 1, b = 2;
- mov dword ptr [a],1
- mov dword ptr [b],2
- CTest test;
- int sum = test.Sum(a, b);
- mov eax,dword ptr [b]
- push eax
- mov ecx,dword ptr [a]
- push ecx
- lea ecx,[test] // 对象指针通过ecx传递
- call CTest::Sum (1911D1h)
- mov dword ptr [sum],eax
- class CTest
- {
- public:
- int Sum(int a, int b)
- {
- push ebp
- mov ebp,esp
- sub esp,0CCh
- push ebx
- push esi
- push edi
- push ecx
- lea edi,[ebp-0CCh]
- mov ecx,33h
- mov eax,0CCCCCCCCh
- rep stos dword ptr es:[edi]
- pop ecx
- mov dword ptr [ebp-8],ecx
- return a+b;
- mov eax,dword ptr [a]
- add eax,dword ptr [b]
- }
- pop edi
- pop esi
- pop ebx
- mov esp,ebp
- pop ebp
- ret 8 // 平衡栈