Visual C++名字修饰Name mangling,或者Decorated Name,是指程序设计语言中具有存储性质的对象的名字被编译器改写,以适合编译器、链接器(linker)、汇编器(assembler)使用[1]。所谓的具有存储性质的对象,即lvalue对象,是指要实际占用内存空间、有内存地址的那些实体对象,例如:变量(variables)、函数、函数指针等。C++中的纯虚函数作为特例也属于这一范畴。而数据类型(data type)就不属于具有存储性质的对象。Name mangling如何翻译成中文,尚无广泛接受的译法。可翻译作改名或名字修饰(name decorating)。 对于支持重载(overload)的程序设计语言,name mangling是必需的因而具有特别重要意义[2]。C++允许函数重载 int foo(int i);
void foo(char c);
C++也允许变量名称在不同限定(qualifier)下同名: std::cout;
::cout;
因此C++的编译器必须给这些函数及变量不同的名字以供程序编译链接、载入时的内部辨别使用。 C++的编译器可大略分为Windows平台上的Microsoft Visual C++与类Unix平台上的GNU CC/g++两大类,分别成了各自操作系统环境下的业界工业标准。例如,Windows平台上的Intel C++ Compiler(ICC)与Digital Mars C++,都与Visual C++保持了二进制兼容(Application Binary Interface, ABI)。而Linux平台上的Intel C++ Compiler(ICC)与HP aC++,都与GCC 3.x/4.x做到二进制兼容。GCC是开源产品,它的内部实现机制是公开的;而Visual C++不公开它内部实现细节,因此在name mangling上并无详尽的正式文档,Visual C++ name mangling的细节属于hacker行为[3]。 一般情况下,编程者不需要知道C/C++函数的修饰名字。但是,如果在汇编源程序或者内联汇编中引用了C/C++函数,就必须使用其正确的修饰名字[4]。 C语言的name manglingC语言并不支持重载,因此C程序中禁止函数与函数同名,也就没有必要做name mangling。但C语言的函数调用协议(calling conventions)五花八门,用某一种调用协议编译的静态库或动态库的函数,如果用另外的调用协议去调用将会导致错误甚至系统崩溃。因此C语言编译器对函数的名字做少量的修饰,用于区别该函数支持哪种调用协议,这可以给编译、链接、特别是库函数的载入提供额外的检查信息。Microsoft C编译器在八十年代最先引入这种mangling模式,随后各家编译器如Digital Mars, Borland, GNU gcc等纷纷效仿。 目前,C语言常用的调用协议有三种:cdecl, stdcall与fastcall,其它众多调用协议如__pascal, __fortran, __syscall, __far等基本上算是过时了(obsolote),因此无需考虑。cdecl的函数被改名为_name;stdcall的函数被改名为_name@X;fastcall的函数被改名为@name@X。其中X是函数形参所占用的字节长度(包括那些用寄存器传递的参数, 如fastcall协议)[5]. 例如: int __cdecl foo(int i); // mangled name is _foo;
int __stdcall bar(int j); // mangled name is _bar@4
int __fastcall qux(int i) ; // mangled name is @qux@4
注意在64位Windows平台上,由于ABI有正式标准可依,只存在一种子程序调用协议,所以该平台上的C语言的函数不在名字前加上下划线(leading underscore). 这会导致一些老的程序(legacy program), 例如使用'alias'去链接C语言的函数的Fortran程序不能正常工作。 C++语言name mangling概述C++语言由于含有大量复杂的语言特性,如classes, templates, namespaces, operator overloading等,这使得对象的名字在不同使用上下文中具有不同的意义。所以C++的name mangling非常复杂。 这些名字被mangled的对象,实际上都是具有全局属性的存储对象(storage object),其名字将绑定到所占用内存空间的地址,都有lvalue。存储对象具体可分为数据变量(data variable)与函数(function)。为什么不考虑函数的非静态局部变量、类的非静态数据成员呢?因为编译器把函数的非静态局部变量翻译为 下文使用了巴科斯-瑙尔范式(BNF)来表述一些name mangling的语法定义。方括号[]表示该项出现0次或1次,除非在方括号后用上下角标给出该项出现的上下限。下文使用类,一般包含了class、struct、union等复合数据类型。 基本结构
C++中的变量与函数,可定义于名字空间或类中。所以变量与函数受到名字空间或类的限定(qualification)。而名字空间、类又可以嵌套(nest)。
Name mangling时,名字的字符串用
数据对象的name mangling这里所说的数据对象,包括全局变量(global variables)、类的静态数据成员变量(static member variables of classes)。
例如: int alpha; // mangled as ?alpha@@3HA 其中3表示全局变量,H表示整形,A表示非const非volatile
char beta[6] = "Hello"; // mangled as ?beta@@3PADA
//其中P表示为指针(或一维数组)且指针自身为非const非volatile,
//D表示char类型,两次出现的A都表示基类型为非const非volatile
class myC{
static int s_v; // mangled as ?s_v@myC@@0HA 其中0表示私有静态数据成员
};
函数的name mangling函数需要分配内存空间以容纳函数的代码,函数的名字实际上都是lvalue,即指向一块可执行内存空间的起始地址。而函数模板的实例化(function template instantiation),也是lvalue,需要分配内存空间存储实例化后的代码,其name mangling在模板实例化的名字编码中详述。
类成员函数的
如果函数的返回值不具有const或volatile性质,那么该项在编码中被省略;但是如果函数的返回类型是class、struct或union等复合数据类型,此项在编码中是必需的,不能省略。
举例: void Function1 (int a, int * b); /*mangled as ?Function1@@YAXHPAH@Z
其中 Y: 全局函数
A: cdecl调用协议
X: 返回类型为void
H: 第一个形参类型为int
PAH:第二个形参类型为整形指针
@: 形参表结束标志
Z: 缺省的异常规范 */
int Class1::MemberFunction(int a, int * b); /* mangled in 32-bit mode as
?MemberFunction@Class1@@QAEHHPAH@Z
其中 Q: 类的public function
A: 成员函数不是const member function
E: thiscall调用协议
H: 返回值为整形
H: 第一个形参类型为整形
PAH: 第二个形参为整形指针 */
C++语言name mangling细节名字的编码C++程序中,需要考虑的具有全局存储属性的变量名字及函数的名字。这些名字受namespace、class等作用域(scope)的限定。因此,带完整限定信息的名字定义为:
其中,
数的编码Visual C++的name mangling,有时会用到数(number),例如多维数组的维数等。数的编码使用一套独特的方法:
例如,8编码为7。29110编码为BCD@。-1510编码为?P@。 特殊名字的编码特殊名字(special names)是指类的构造函数、析构函数、运算符函数、类的内部数据结构等的名字,表示为前缀
虚表的mangled name是 前缀 下表是RTTI相关的编码,都是在
模板实例化的名字编码函数模板实例化后,就是一个具体的函数。类模板实例化后,就是一个具体的类数据类型。
模板实参(template argument),可以分为类型名字(typename)与非类型(non-type)的常量两类。如果是类型名字或类作为模板实参,那么其编码格式详见类型的编码表示。如果模板实参是常量(constant),则已知的编码格式列为下表:
上表中,用a, b, c表示有符号整数,而x, y, z表示无符号整数. 这些有符号整数或无符号整数的编码格式,详见数的编码。上表中,实数值的编码表示 例如: template<class T> class one{
int i;
};
one<int> one1; // mangled as ?one1@@3V?$one@H@@A
//3V...@A表示这是一个全局的类对象,其中的@是类的编码的结束标志;
//类名为one<int>,编码是?$one@H@,其中one@是模板的名字,
//H是模板实参int,其后的@是模板实参表结束标志
class Ce{};
one<Ce> another; /* mangled as ?another@@3V?$one@VCe@@@@A */
//注意,倒数第1个@表示整个(模板实例)类的结束;
// 倒数第2个@表示模板实参表的结束;
// 倒数第3个@表示类Ce的限定情况的结束(此处限定为空,即Ce的作用域是全局);
// 倒数第4个@表示类名码串的结束
编号名字空间编号名字空间(numbered namespace)用于指出函数静态局部变量包含在函数的哪个内部作用域中。之所以需要引入编号名字空间,是为了区分函数内部的不同作用域,从而可以区分包含在不同作用域中但同名的变量,详见下例。其编码格式为前缀?后跟一个无符号数,无符号数的编码参见数的编码小节。 特例情况是, 以?A开始的编号名字空间, 是 例如: int func()
{
static int i; // mangled as ?i@?1??func@@YAHXZ@4HA 内部表示为`func'::`2'::i
// ?1表示第2号名字空间;?func@@YAHXZ@是函数的mangled名字;4表示静态局部变量
{
static int i; // mangled as ?i@?2??func@@YAHXZ@4HA 内部表示为`func'::`3'::i
// ?3表示第3号名字空间
}
return 0;
}
重复出现的名字与类型的简写在对一个名字做mangling时,用简写方法表示非首次出现的同一个名字或同一个类型。整个简写过程需要对ASCII码串做3次扫描处理:
重复出现的类型的简写这适用于函数与函数指针的形参列表。只有编码超过一个字符的类型参与简写,包括指针、函数指针、引用、数组、bool、__int64、class、实例化的模板类、union、struct、enum等数据类型。形参表中前10种多字符编码的类型按照出现次序依次编号为0,1,...,9。用单个字符编码的类型不参加编号。对不是该数据类型首次出现的形参,用该类型的单个数字的编号代替该数据类型的多个字符的编码来简写表示。排在前十名之后的多字符编码的数据类型,不再简写。函数的返回值类型不参与此编号及简写。 如果函数的返回类型或者形参是函数指针型,那么函数指针型的形参也参与类型排序编号与简写,但函数指针型的返回值类型不参与类型排序编号与简写。在对类型排序编号时,先编号函数指针型内部的形参的数据类型,再编号函数指针型本身。例如,假如函数的第一个形参是 例如: bool ExampleFunction (int*a, int b, int c, int*d, bool e, bool f, bool*g);
// mangled as ?ExampleFunction@@YA_NPAHHH0_N1PA_N@Z
// 其中,_N为返回类型bool,不参与类型简写,不参与编号排序;
// 类型的排序编号:int*为0,bool为1,bool*为2。
// int的编码为单字符H,因此不参与编号
// 第3个形参不简写,仍为H;第四个形参简写为0,第五个形参简写为1 */
//
typedef int* (*FP)(int*); /* 该函数指针类型编码为 P6APAHPAH@Z */
//
FP funcfp (int *, FP) /* mangled as ?funcfp@@YAP6APAHPAH@Z0P6APAH0@Z@Z */
// 其中P6A为函数指针类型的编码前缀
// 其中共出现了5次int* (编码为PAH)
// 第1次为funcfp的返回值类型FP的返回值类型,不参与排序编号与简写;
// 第2次为funcfp的返回值类型FP的形参,参与排序编号,编号为0,
// 因为是该类型int*的首次作为形参出现,不简写,仍编码为PAH
// 第3次为funcfp的第一个形参,简写为0;
// 第4次为funcfp的第二个形参的返回值类型,不参与排序编号与简写;
// 第5次为funcfp的第二个形参的参数,简写为0
{return 0;}
重复出现的名字的简写在对码串完成重复出现的类型的简写后,再对结果中所有不同的名字排序编号,从0编号到9。排在前10个之后的名字不再编号、简写。这里的名字是指函数、class、struct、union、enum、实例化的带实参的模板等等的以 class C1{
class C2{};
};
union C2{};
void func(C2,C1::C2) /* mangled as ?func@@YAXTC2@@V1C1@@@Z */
//其中,func的形参表是TC2@@V1C1@@@
//TC2@@是第一个形参union C2;
//V1C1@@是第二个形参C1::C2,其中V表示这是class,
//首个1表示是编号为1的名字(即C2@)重复出现,随后的C1@表示C2的限定域为C1
//随后的@表示限定域的嵌套结束。注意,该字串中编号为“0”的名字是func@
{}
对于实例化模板,模板名字后跟模板实参作为一个整体视作一个名字,参加此轮排序编号与简写。而模板实参表中的参数序列,单独处理它的编号及简写。例如: template<class T> class tc{
public: void __stdcall func(tc<T>){};
};
int main(int argc, char *argv[])
{
tc<int> ins;
ins.func(ins); /* tc<int>::func(tc<int>) mangled as ?func@?$tc@H@@QAEXV1@@Z */
//其中,?$tc@H@表示实例化的类模板tc<int>,里面的H表示模板实例化的实参是整型
//Q表示public的成员函数;A表示非只读成员函数;E表示thiscall
//X表示函数返回类型void;
//V1@表示形参为一个class,class的名字为''1''号名字,本例中即tc<int>的编码?$tc@H@;
//注意,本例中''0''号名字是函数名,即func@
//最后一个@表示函数的形参表结束;Z表示缺省的exception specification
return 0;
}
模板实例化时重复出现的实参的简写模板实例化的名字编码,基本上就是用模板的名字与模板实参作为一个整体,当作 例1: template<class T1, class T2> class tc{
int i;
public: void __stdcall func(tc<T1,T2> p1,tc<T1,T2> p2){};
};
class Ce{};
int main(int argc, char *argv[])
{
tc<Ce,Ce> ins;
ins.func(ins,ins); /* void tc<Ce,Ce>::func(tc<Ce,Ce>,tc<Ce,Ce>)
mangled as ?func@?$tc@VCe@@V1@@@QAGXV1@0@Z */
//?$tc@VCe@@V1@@表示实例化的类模板tc<Ce,Ce>,其中的V1@是类模板的实参的重复名字简写;
//函数func的作用域是tc<Ce,Ce>,即tc<Ce,Ce>::func编码为func@?$tc@VCe@@V1@@@ ;
//V1@表示成员函数func的第一个形参为一个类,类名为''1''号名字,本例中即tc<Ce,Ce>,这是重复名字的简写;
//0表示该函数func的第二个形参与形参表中的''0''号形参相同,即tc<Ce,Ce>,这是重复类型的简写
return 0;
}
例2: class class1{
public: class class2{} ee2;
};
union class2{};
template <class T1, class T2, class T3> void func( T1,T2,T3 ){}
int main(int argc, char *argv[])
{
class1 a;
class2 b;
func<class2, class1::class2, class2>(b,a.ee2,b);
/* func<class2,class1::class2,class2>(class2,class1::class2,class2)
mangled as ??$func@Tclass2@@V1class1@@T1@@@YAXTclass2@@V0class1@@0@Z */
//如果不简写: ??$func@Tclass2@@Vclass2@class1@@Tclass2@@@@YAXTclass2@@Vclass2@class1@@Tclass2@@@Z
// 首先对函数的形参表中重复的类型简写,第3个参数与第1个参数相同,以类型编号0简写;
// 然后对整个字串中重复的名字编号、简写,第1个形参中的名字‘class2@’编号为0,
// 所以函数第2个形参中的‘class2@’被简写为0;
// 这也说明“函数模板名+模板实参”,不作为名字参与编号及简写(但普通函数的名字却是参与名字编号!),
// 而类模板名+模板实参”却算作单独一个名字而参与编号及简写;
// 之后,对模板实例化名字,即“函数模板名+模板实参”,单独执行重复出现的名字编号及简写,
// 其中的‘class2@’出现3次,后2次被简写为1,这也说明在模板实例化名字内部,
// 仅执行重复名字的简写,不执行重复类型的简写
return 0;
}
例3: class class1{
public: class name9{} ee;
};
template <class T> void name9 ( T p1){}
int main(int argc, char *argv[])
{
class1 vv;
name9<class1::name9>(vv.ee); /* void name9<class1::name9>(class1::name9)
mangled as ??$name9@V0class1@@@@YAXVname9@class1@@@Z */
// 此例说明模板实例化在做重复名字简化时,
// 模板实参中的name9与模板名字name9相同,因而简化为编号0
return 0;
}
数据类型的编码表示这里所说的类型,包括数据类型、函数指针的类型、函数模板、类模板等不需要分配内存空间的一些概念属性。类型是数据对象与函数这两类实体的属性。
对于简单的数据类型,其编码往往就是一个字母。如int类型编码为X。对各种衍生的数据类型(如指针)、复合的数据类型(如类)、函数指针、模板等,在下文中分述。
指针、引用、数组的类型编码
对于函数指针类型的编码,其 2003年x86-64位处理器问世后,第一批64位Windows平台的C++编译器曾经使用 需注意的是,全局数组类型被编码为P(指针型),同时作为函数形参的数组类型被编码为Q(常量指针). 这与其本来含义恰恰相反——全局数组型的变量名字表示某块内存地址,该名字不能再改为指向其它内存地址;而作为函数形参的数组型变量的名字所表示的内存地址是可以修改的。但数组类型这种编码方法已经被各种C++编译器广泛接受。显然,这是为了与老的代码保持向后兼容。例如: int ia[10]; //ia是数组类型的非函数形参的变量
//
int main(int argc, char *argv[]) //argv是数组类型的形参, 其类型为 (char *)[]
{
int j=*(ia++); //编译错误!ia是只读的lvalue,不能完成地址的++操作
char *c= *(argv++); //编译正确!argv是可以修改的lvalue
return 0;
}
例如: typedef int * p1; // coded as PAH 其中P表示default访问属性的指针,
//A表示对基类型的default访问属性,H表示基类型为int
typedef const int * p2; //coded as PBH 其中B表示基类型的const访问属性
typedef volatile int *p3; //coded as PCH 其中C表示基类型的volatile访问属性
typedef volatile const int *p4; //coded as PDH 其中D表示基类型的const volatile访问属性
typedef int * const p5; //coded as QAH 其中Q表示是const pointer
typedef int * volatile p6; //coded as RAH 其中R表示是volatile pointer
typedef int * const volatile p7; //coded as SAH 其中S表示是const volatile pointer
typedef volatile int * const p6; //coded as QCH 例如这是一个外部IO设备输入数据的内存地址
typedef int &r1; // coded as AAH 其中第一个A表示左值引用类型(l-value reference type)
typedef int&& r2; //coded as $$QAH 其中$$Q表示是右值引用类型(r-value reference type)
typedef const int&& r3; //coded as $$QBH 其中B表示基类型是const属性
typedef int[8] a1; // global array coded as PAH
typedef int[10][8] a2; //global array coded as PAY07H 其中Y标志多维数组,0是(维数-1)即1的编码
// 7是第二维长度8的编码
typedef int[4][16][5] a3; //global array coded as PAY1BA@4H 其中1为(维数-1)即2的编码
//BA@是第二维长度16的编码(16进制的10),4是第3维长度5的编码
int[7][6] // 作为函数形参时,该数据类型编码为 QAY05H
函数指针类型的编码函数指针的类型信息,包括函数返回类型,函数形参类型,调用协议等。以前缀
例如: typedef const int (__stdcall *FP) (int i); /* coded as P6G?BHH@Z */
// 其中?B表示<storage ret>为const
类成员指针的类型编码指向类成员的指针,其编码为
例如: class C1{
int i;
};
typedef int C1::*p; // coded as PQC1@@H
类成员指针变量的mangled name类成员指针(pointer to member)的名字mangling的最末尾处对基类型访问属性的编码不同于普通的指针,要在最后加上所指向类的带完整限定信息的名字:
有的文献称[7],类成员指针、类成员函数指针的name mangling都必须以Q1@作为结尾,以替代 下例中,成员指针变量的mangled name以S12@结尾: class outer{
public:
class cde{
public: volatile int i;
};
};
volatile int outer::cde::* p; // mangled as ?p@@3PScde@outer@@HS12@
// 其中两个S都是表示基类型为volatile属性
// 1是cde@的简写
// 2是outer@的简写
例2: class C1{
public: int i;
};
C1 const *pi; // ?pi@@3PBVC1@@B 一个简单的指针变量。作为对比
typedef int C1::* TP; // coded as PQC1@@H
void func(TP){
static TP ppp=0; // `func'::`2'::ppp mangled as ?ppp@?1??func@@YAXPQC1@@H@Z@4PQ2@HQ2@
//其中,?ppp@?1??func@@YAXPQC1@@H@Z@表示带作用域限定信息的名字`func'::`2'::ppp
//4表示静态局部变量;PQ2@H表示成员指针类型,其中的“2”是C1@的简写。
//注意ppp@编号为0,func@编号为1
//最后三个字符Q2@表示对基类型“2”(C1@的简写)的访问属性为Q(缺省属性,即非const非volatile)
}
类成员函数指针的类型编码类成员函数的指针(pointer to member function),遵从指针类型编码的一般规则。但与函数指针类型的编码相比,多了一项
例如: class C1{
public: void foo(int) const
{};
};
typedef void (C1::*TP)(int) const; /* coded as P8C1@@BEXH@Z
其中B表示const member function; E表示thiscall */
类成员函数的指针变量的mangled name类成员函数指针(pointer to member function)的名字mangling,对基类型访问属性的编码
例如: class xyz{
public: void foo(int) {};
};
void (xyz::*pfunc)(int) ; /* mangled as ?pfunc@@3P8xyz@@AEXH@ZQ1@ */
// 其中Q表示对基类型的访问属性为default;1表示被简写的编号为‘1’的名字,即‘xyz@’;
// 注意,编号为‘0’的名字是‘pfunc@’
复合类型(union, struct, class, coclass, cointerface)的编码
其中复合类型的种类作为前缀,union编码为T, struct编码为U, class编码为V, coclass编码为X, cointerface编码为Y。复合类型的带限定的名字 经常可以看到复合类型的编码以@@两个字符结尾,这是因为第一个@表示复合类型名字的字串结束,第二个@表示限定情况的结束(即作用域为全局,限定情况为空)。 编写代码时,经常要用到类的前向声明(forward declaration),即提前声明这个名字是个类,但类的成员尚未给出。例如: class myClassName; //mangled type name is VmyClassName@@
class myClassName::embedClassName; //mangled type name is VembedClassName@VmyClassName@@
枚举类型(enum)的编码
例如: enum namex:unsigned char {Sunday, Monday}; // enum-type coded as W4namex@@
看起来Visual C++已经把所有枚举类型用int型实现,因此枚举的基类型(The underlying type of the enumeration identifiers)的编码总是为4
|
Variable | Function | ||||
---|---|---|---|---|---|
none | const | volatile | const volatile | ||
none | A
|
B , J
|
C , G , K
|
D , H , L
|
6 , 7
|
__based() | M
|
N
|
O
|
P
|
_A , _B
|
Member | Q , U , Y
|
R , V , Z
|
S , W , 0
|
T , X , 1
|
8 , 9
|
__based() Member | 2
|
3
|
4
|
5
|
_C , _D
|
<CV Modifier>
可以有0个或多个前缀:
Prefix | Meaning |
---|---|
E
|
type __ptr64 |
F
|
__unaligned type |
I
|
type __restrict |
__based()属性的变量
指针变量的__based()属性是Microsoft的C++语言扩展. 这一属性编码为:
0
(意味着__based(void)
)2<Qualified Name>
(意味着__based(<Qualified Name>)
)5
(意味着没有__based()
)
例如:
int *pBased; // mangled name: ?pBased@@3PAHA
int __based(pBased) * pBasedPtr; // 需要注意Visual C++编译器把这个指针变量的声明解释为:
// (int __based(pBased) * __based(pBased) pBasedPtr)
// 因此其mangled name: ?pBasedPtr@@3PM2pBased@@HM21@
// 其中PM2pBased@@表示这是基于<::pBased>的指针;HM21表示是基于“1”的整型指针,
// “1”是重复出现的名字的编号简写,这里就是指pBased@
//
int __based(void) *pbc; // mangled name: ?pbc@@3PM0HM0 其中的0表示这是__based(void).
// 编译器把该变量声明解释为(int __based(void) * __based(void) pbc)
函数的类型信息编码
函数的类型信息,是指调用函数时必须考虑的ABI(Application Binary Interface),包括调用协议、返回类型、函数形参表、函数抛出异常的说明(exception specification)等,参见函数的name mangling。
<func modifier>
<func modifier>
给出了函数是near或far(但far属性仅适用于Windows 16位环境,32位或64位环境下只能函数具有near属性)、是否为静态函数、是否为虚函数、类成员函数的访问级别等信息:
near | far | static near | static far | virtual near | virtual far | thunk near | thunk far | |
---|---|---|---|---|---|---|---|---|
private: | A |
B
|
C |
D
|
E |
F
|
G |
H
|
protected: | I |
J
|
K |
L
|
M |
N
|
O |
P
|
public: | Q |
R
|
S |
T
|
U |
V
|
W |
X
|
not member | Y |
Z
|
上表中的thunk函数[8],是指在多继承时,由编译器生成的包装函数(warpper function),用于多态调用实际已被子类对应函数覆盖(overrided)的父类虚函数,并把指向父类的this指针调整到指向子类的起始地址。
调用协议的编码
Code | Exported? | Calling Convention |
---|---|---|
A
|
No | __cdecl |
B
|
Yes | __cdecl |
C
|
No | __pascal __fortran |
D
|
Yes | __pascal |
E
|
No | __thiscall |
F
|
Yes | __thiscall |
G
|
No | __stdcall |
H
|
Yes | __stdcall |
I
|
No | __fastcall |
J
|
Yes | __fastcall |
K
|
No | none |
L
|
Yes | none |
M
|
No | __clrcall |
64位编程时,唯一可用的调用协议的编码是A
查看Visual C++的函数的修饰后的名字
有多种方法,可以方便地查看一个函数在编译后的修饰名字[9]:
- 直接用工具软件(如微软开发环境提供的dumpbin)查看obj、exe等二进制文件。使用dumplib查看.obj或.lib文件时,使用"/SYMBOLS"命令行选项。[10]
- 编译时使用"/FA[c|s|u]"编译选项,生成带有丰富注释信息的汇编源程序,其文件扩展名是.cod或者.asm,可以查看每个C/C++函数的修饰名字[11]。
- 在源程序中使用微软提供的预定义宏(Microsoft-Specific Predefined Macros)—— __FUNCDNAME__,例如:
void exampleFunction()
{
printf("Function name: %s\n", __FUNCTION__);
printf("Decorated function name: %s\n", __FUNCDNAME__);
printf("Function signature: %s\n", __FUNCSIG__);
// 输出为:
// -------------------------------------------------
// Function name: exampleFunction
// Decorated function name: ?exampleFunction@@YAXXZ
// Function signature: void __cdecl exampleFunction(void)
}
由修饰名字反查其未修饰时的原名
- 使用微软Visual C++中的解析修饰名字的工具软件undname.exe。例如:
C:\>undname.exe ??$name9@V0class1@@@@YAXVname9@class1@@@Z
Microsoft (R) C++ Name Undecorator
Copyright (C) Microsoft Corporation. All rights reserved.
Undecoration of :- "??$name9@V0class1@@@@YAXVname9@class1@@@Z"
is :- "void __cdecl name9<class class1::name9>(class class1::name9)"
- 使用Windows提供的系统调用UnDecorateSymbolName()[12]把修饰名字翻译为未修饰名字。UnDecorateSymbolName在DbgHelp.h或imagehlp.h中声明,在DbgHelp.dll中实现,需要使用导入库DbgHelp.lib。Windows SDK中包含了DbgHelp.h与DbgHelp.lib。示例程序:
//UnDecorate.cpp
#include <windows.h> //如果不包含此头文件,编译DbgHelp.h时会产生大量语法错误
#include <DbgHelp.h>
#include <tchar.h>
#include <iostream>
#pragma comment(lib,"dbghelp.lib") //告诉链接器使用这个输入库
int _tmain(int argc, _TCHAR* argv[])
{
TCHAR szUndecorateName[256];
memset(szUndecorateName,0,256);
if (2==argc)
{
::UnDecorateSymbolName(argv[1],szUndecorateName,256,0);
std::cout<<szUndecorateName<<std::endl;
}
return 0;
}
编译后,执行上述程序:
C:\>UnDecorate.exe ?apiname@@YA_NEEPAD@Z
bool __cdecl apiname(unsigned char,unsigned char,char *)
C++修饰名字的用途
DLL输出的C++函数
在Windows平台上,使用dllexport关键字直接输出C++函数时,DLL的用户看到的是修饰后的函数名字[13]. 如果不希望使用复杂的C++修饰后的函数名,替代办法是在DLL的.def文件中定义输出函数的别名,或者把函数声明为extern "C".
在汇编源程序或者内联汇编中引用C/C++函数
在汇编源程序或者内联汇编中引用了C/C++函数,就必须引用该函数的修饰名字。
参考文献
- ^ 微软MSDN的定义是:“A decorated name is a string created by the compiler during compilation of the function definition or prototype.”. [2012-08-11]. (原始内容存档于2016-10-10).
- ^ 微软MSDN的《Using Decorated Names》:"You must specify the decorated name of C++ functions that are overloaded ... ..., in order for LINK and other tools to be able to match the name. ". [2012-08-11]. (原始内容存档于2016-05-06).
- ^ 微软MSDN的《Format of a C++ Decorated Name》
- ^ 微软MSDN的《Using Decorated Names》:"You must also use decorated names in assembly source files that reference a C or C++ function name.". [2012-08-11]. (原始内容存档于2016-05-06).
- ^ 微软MSDN的《Format of a C Decorated Name》. [2012-08-11]. (原始内容存档于2016-04-02).
- ^ 参见VC++ Compiler Warning C4290
- ^ Calling conventions for different C++ compilers (页面存档备份,存于互联网档案馆) pp29:"This code is replaced by Q1@ for member pointers and member function pointers, regardless of storage class."
- ^ 参见Thunk (object-oriented programming)
- ^ 微软MSDN的《Viewing Decorated Names》
- ^ 微软MSDN的《Using DUMPBIN to View Decorated Names》
- ^ 微软MSDN的《Using a Listing to View Decorated Names》. [2012-08-11]. (原始内容存档于2016-05-09).
- ^ MSDN的帮助文章《UnDecorateSymbolName function》
- ^ 微软的MSDN关于"dllexport, dllimport"的帮助文章. [2012-07-29]. (原始内容存档于2012-07-09).