现在的位置: 首页 > 综合 > 正文

函数调用约定

2017年12月17日 ⁄ 综合 ⁄ 共 2854字 ⁄ 字号 评论关闭

函数调用约定就是描述参数如何传递,堆栈由调用方还是被调用方平衡,返回值如何返回等规则。

函数调用约定的几种类型有:__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

__stdcall与__cdecl除了栈平衡任务由谁来完成以外,其余部分一样。

(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     // 平衡栈

抱歉!评论已关闭.