函数调用约定
什么是函数调用约定?
调用约定(Calling convention)决定以下内容:
- 函数参数的压栈顺序
- 由调用者还是被调用者把参数弹出栈
- 产生函数修饰名的方法。
关于 C/C++ 函数调用约定,大多数时候并不会影响程序逻辑,但遇到跨语言编程时,了解一下还是有好处的。
VC 中默认调用是 __cdecl 方式,Windows API 使用 __stdcall 调用方式,在 DLL 导出函数中,为了跟 Windows API 保持一致,建议使用 __stdcall 方式。
调用约定跟堆栈清除密切相关。如果写一个汇编函数,给 C/C++ 调用,在 __cdecl 方式下,则汇编函数无需清除堆栈,在 __stdcall 方式下,汇编函数需要在返回(RET)之前恢复堆栈。
MFC支持以下调用约定:
- C 语言有 __cdecl、__stdcall、__fastcall、naked、__pascal。
- C++ 语言有 __cdecl、__stdcall、__fastcall、naked、__pascal、__thiscall,比 C 语言多出一种 __thiscall 调用方式。
常用6种描述编辑
__cdecl
按从右至左的顺序压参数入栈,由调用者把参数弹出栈。
- 对于“C”函数或者变量,修饰名是在函数名前加下划线。(如函数void test(void)的修饰名是_test)
- 对于“C++”函数,有所不同。 (对于不属于一个类的“C++”全局函数,修饰名是?test@@ZAXXZ。)
这是MFC缺省调用约定。由于是调用者负责把参数弹出栈,返回值在EAX中,所以可以给函数定义个数不定的参数,如printf函数。
__stdcall
__stdcall 很多时候被称为 pascal 调用约定。pascal 语言是早期很常见的一种教学用计算机程序设计语言,其语法严谨。参数按照从右至左的顺序压参数入栈,由被调用者把参数弹出栈,返回值在EAX中。所有的Win32 API函数都遵循该约定。
__fastcall
顾名思义,__fastcall 的特点就是快,因为它通过 CPU 寄存器来传递参数。他用 ECX 和 EDX 传送前两个双字(DWORD)或更小的参数,剩下的参数按照从右至左的方式入栈,函数自身清理堆栈,返回值在 EAX 中。
__thiscall
这是 C++ 语言特有的一种调用方式,用于类成员函数的调用约定。 如果参数确定,this 指针存放于 ECX 寄存器,函数自身清理堆栈;如果参数不确定,this指针在所有参数入栈后再入栈,调用者清理栈。 __thiscall 不是关键字,程序员不能使用。参数按照从右至左的方式入栈。
naked
naked 是一个很少见的调用约定,一般不建议使用。编译器不会给这种函数增加初始化和清理代码,更特殊的是,你不能用return返回返回值,只能用插入汇编返回结果,此调用约定必须跟 __declspec 同时使用。
__pascal
这是 pascal 语言的调用约定,跟 __stdcall 一样,参数按照从右至左的方式入栈,函数自身清理堆栈,返回值在EAX中。VC 中已经废弃了这种调用方式,因此在写 VC 程序时,建议使用 __stdcall 代替。