一个完整的函数声明包括返回值类型,调用协议名称,函数名称,参数信息等若干部分,为了把函数的所有原型信息记录在单一的字符串中以便于标识和组织函数,VC编译器使用了一种称为名称修饰(Name Decoration)的技术,其宗旨就是将函数的本来名称,调用协议,返回值信息按照一定的规则编排成一个新的名字,称为修饰名称(Decorated Name)。下面看一个函数TestFunction的原型和它的修饰名称
int TestFunction ( HWND hWnd, int n)
修饰名称 ?TestFunction@@YAHPAUHWND__@@H@Z
修饰后的名称不再包含空格和括号这些不便于存储的分隔符,将多个部分合并一个连贯的单一整体,因此名称修饰有时也被称为碾平(Name Mangling)。
C编译器和C++编译器使用的名称修饰规则是不同的,这意味着对VC这样的同时支持C和C++语言,同一个函数可能产生不同的修饰名。例如,对于void __cdecl test (void) 函数,按照C规范编译所产生的修饰名称是_test, 而按照C++规范编译产生的修饰名称是?test@@ZAXXZ。因为连接器连接目标文件时使用的修饰名称来连接的,所以,如果调用方和实现放所使用的编译规范不同,那么连接时会出现下面的错误:
Error LNK2001: unresolved external symbol “void __cdecl Test (void)” (?Test@@YAXXZ)
VC编译器默认按照源文件的扩展名来决定源文件的类型和选择编译器,.c代表C文件,.cpp和.cxx代表C++文件,同时也支持使用编译选项/Tc,/Tp,/TC和/TP选项来选择编译器。
其中/Tc后跟文件名表示强制文件为C文件,/Tp后跟文件名表示强制此文件为C++文件,使用/TC选项则强制指定所有要编译的文件都是C文件,使用/TP选项,强制指定所有要编译的文件都是C++文件,在C++文件中,使用extern “C” 关键字声明使用C的名称修饰规则。C++的修饰名称都是以问号?开始的。
C语言通过不同的调用协议来产生修饰名称,当使用__cdecl(C调用协议)时,会在函数名称前加一个下划线,不考虑参数和返回值。使用__fastcall函数,在函数名称前后各加一@符号,后跟参数长度,不考虑返回值。例如extern “C” int __fastcall Test(int n)的修饰名称为@Test@4. 对于使用标准调用协议(__stdcall)的函数,在函数名称前加一下划线,后跟参数长度,不考虑返回值。如extern “C” int __stdcall Test (int n, int m) 的修饰名称为_Test@8
C++名称规则要比C复杂一些,需要考虑类名和命名空间信息,不同的编译器使用的规则是不同的,即使是同一种编译器,不同的版本间可能也存在差异。以Visual C++ 编译器为例,如下所示:
1. 问号前缀
2. 构造函数和析构函数属于特殊函数名,用?0和?1,new, delete, = , + 和 ++的名称分别为?2,?3,?4,?H和?E
3. 如果名称不是特殊函数名,那么加一个分隔符@
4. 如果是类的方法,那么由所属类开始依次加上类名和父类名,每个类名后面跟一个@符号,所有类名加好后,再加一个@符号和字符Q或S(静态方法),如果不是类的方法,那么直接加上@符号和字符Y。
5. 调用协议代码,对于不属于任何类的函数,C调用协议(__cdecl)的代码为A,__fastcall 协议的代码为I,标准调用协议(__stdcall)的代码为G。对于类的方法,调用协议前会加一个字符A,this调用协议的代码为E。
6. 返回值编码,例如字符H代表整数类型的返回值
7. 参数列表编码,以@符号结束
8. 后缀Z。
总的来说
1. 都是以?开始,以字符Z结束,中间由@符号分割为多个部分,另外整个名称的长度最长为2048个字符。
2. 对于类的函数,其基本结构为:?方法名@类名@@调用协议 返回类型 参数列表Z
3. 对不属于任何类的函数,其基本结构为: ?函数名@@Y调用 返回类型 参数列表Z