程序出链接错误的时候,经常看到lnk errorxxx:某某函数、某某变量找不到等等,里面的函数名通常都很难看明白,因为使用的是修饰名。
C 和 C++ 程序中的函数在内部通过其修饰名加以识别。修饰名是在编译函数定义或函数原型期间由编译器创建的字符串。
既然是编译器创建的字符串,不同的编译器使用的修饰名都不一样,这里讲的是visual studio的
C 函数的修饰形式取决于其声明中使用的调用约定。有三种调用约定,如下所示。
调用约定 |
修饰 |
---|---|
__cdecl(默认值) |
前导下划线 (_) |
__stdcall |
前导下划线 (_) 和结尾 at 符 (@),后面跟表示参数列表中的字节数的数字 |
__fastcall |
与 __stdcall 相同,但前置符不是下划线,而是 @ 符 |
__cdecl是C/C++函数的默认调用约定。其堆栈由调用该函数的调用者(caller)来负责清理,因此该函数不需要知道自己有多少个参数,可以拥有可变参数(是vararg function)。也正因为如此,caller里面要有清理堆栈的代码(这里不是指程序员写的C/C++代码,而是编译器编译链接时要加上的二进制代码),所以以__cdecl方式调用子函数比用__stdcall方式调用,生成的可执行文件要更大一些。
参数传递顺序 从右到左
堆栈清理 调用者将参数弹出栈
修饰名 函数名前有下划线_,除非以C链接方式输出__cdecl函数
函数名 函数名没有变化
一个类的非静态的函数成员,如果函数定义在类体外,那么在类体外的函数定义不用加上调用约定。例如:
有一个类定义如下:
struct CMyClass {
void __cdecl mymethod();
};
则类体外的定义
void CMyClass::mymethod() { return; }和void __cdecl CMyClass::mymethod() { return; }是等同的。
__stdcall,所有的windowsAPI都以__stdcall方式调用。因为windows.h里有定义:
#define WINAPI __stdcall
__stdcall方式调用函数的时候,由被调用函数(callee)清理堆栈,所以参数个数必须确定。
参数传递顺序 从右到左
堆栈清理 被调用者将参数弹出栈
参数传递 传值的拷贝,除非参数是指针或者引用
修饰名 函数名前有下划线_,函数名后面跟@,然后是所有参数的字节数(十进制)
函数名 函数名没有变化
因此函数int func( int a, double b )编译后的修饰名是_func@12 (int4字节,double8字节,共12字节)
一个类的非静态的函数成员,如果函数定义在类体外,那么在类体外的函数定义不用加上调用约定。例如:
有一个类定义如下:
struct CMyClass {
void __stdcall mymethod();
};
则类体外的定义
void CMyClass::mymethod() { return; }和void __stdcall CMyClass::mymethod() { return; }是等同的。
__fastcall指的是如果可能,通过寄存器传递参数(所以叫做fast call)
参数传递顺序 前两个DWORD(unsigned long,32bit)或者更小尺寸的参数
(The first two DWORD or smaller arguments )通过ECX和EDX寄存器传递,
其余的从右到左
堆栈清理 调用者将参数弹出栈(所以参数个数也必须确定)
修饰名 函数名前有@,函数名后跟@,然后是参数的总字节数
函数名 函数名没有变化
因此函数int func( int a, double b )编译后的修饰名是 @func@12 (int4字节,double8字节,共12字节)
关于参数传递顺序,我的理解是:int func(DWORD i,DWORD j,int x,int y)当然是i、j分别用ECX和EDX传递,x、y从右到左通过栈传递。
int func(DWORDLONG i,DWORDLONG j,int x,int y),是x、y分别用ECX和EDX传递,64位的i和j从右到左通过栈传递
一个类的非静态的函数成员,如果函数定义在类体外,那么在类体外的函数定义不用加上调用约定。例如:
有一个类定义如下:
struct CMyClass {
void __fastcall mymethod();
};
则类体外的定义
void CMyClass::mymethod() { return; }和void __fastcall CMyClass::mymethod() { return; }是等同的。
MSDN里给了调用约定的使用例子
// Example of the __cdecl keyword on function
int __cdecl system(const char *);
// Example of the __cdecl keyword on function pointer
typedef BOOL (__cdecl *funcname_ptr)(void * arg1, const char * arg2, DWORD flags, ...);
比较有意思的是 function pointer,我承认之前自己没有function pointer这个概念。特意到网上搜了下,转了篇博文Function Pointer(C)和Function Object(C++)
MSDN里还特别说明了,对于安腾处理器以及X64处理器,调用约定__cdecl和__stdcall被忽略。
对于安腾处理器以及AMD64(这里不是X64,所以不包括Intel-64)处理器,调用约定__fastcall被忽略。
在安腾处理器中,上述三种调用约定的参数全部通过寄存器传递。(不过对我们普通开发者没什么意义,与我何干)
visual studio里设置默认调用约定:项目属性-》C/C++ -》高级-》调用约定
建立新项目的时候默认调用约定是__cdecl。