Name-mangling是指为了在目标文件符号表中和连接过程中使用的名字通常和编译目标文件的源程序中的名字不一样,编译器将目标源文件中的名字进行调整。Name-mangling不是一个非常新的技术,例如在C语言中也有,我们在汇编C语言时经常看到的以下划线“_”开头的函数名,其实就是C编译器将函数名进行了Name-mangling。
但是在C++中Name-mangling要复杂的多。因为C++中支持overload和override,这就导致了C++编译器必须要有完成的Name-mangling把函数名或者变量名进行调整。
一种Name-mangling的方法(选自Linker and loader):在C++类外的数据变量的名字完全不进行调整。一个叫做foo的数组的名字调整后还是foo。没有与类相关的函数名字的调整是根据参数类型用__F后缀和一串代表参数类型的字母进行的。
参数类型一般会被进行简写(如void→v, int→i, float→f, char→c, double→d, varages→e, long→l, unsigned→U, const→C, volatile→V, pointer→P, reference→R, function→f, pointer to nth members→MnS等等)。举例,一个函数是func(float,int,unsigned char)可能变成func__FfiUc。
类的名字被认为是有类型的,被编码成类的名字的长度后面跟着名字,如4Pair。类还可以含有多个层次的内部类的名字;这些”合法的”名字用Q来编码,后面是一个数字标明层次的数目,然后是类的名字,这样First::Second::Third变成了Q35First6Second5Third。也就是说一个有两个类参数的函数f(Pair,First::Second::Third)变成了f__F4PairQ35First6Second5Third。
类成员函数编码成函数名字,然后是两个下划线,然后是编码后的类的名字,然后是F和参数,这样,cl::fn(void)变成fn__2clFv。所有的操作符也都编码成4或5个字符的名字,如__ml代表*,而__aor代表|=。特殊的函数,包括构建器,解构器,new和delete也都编码成__ct,__dt,__nw和__dl。一个类Pair的构建器需要两个字符指针参数,Pair(char*,char*)变成了__ct__4PairFPcPc。
最后,因为调整后的名字会非常长,对带有多个相同类型参数的函数还有两种快速编码方法。代码Tn代表”和第n个参数相同的类型”,而Nnm代表”n个和第m个参数类型相同的参数”。一个函数segment(Pair,Pair)会变成segment__F4PairT1,而一个函数trapezoid(Pair,Pair,Pair,Pair)会变成trapezoid__F4PairN31。
名字调整完成了对每个可能的C++对象都有一个唯一的名字的任务,代价就是在错误信息和列表的时候的极其长而且(如果没有连接器和调试器的支持的话)很难读的名字。
虽然如此,C++本来就存在着一个可能的巨大的名字空间的问题。任何表示C++对象的名字的机制都会和名字调整一样复杂,而调整的名字还有对某些人类是可读的优点。
名字调整的早期用户发现尽管连接器理论上支持长的名字,在实际中很长的名字并不能很好地工作,而当连接带有很多直到最后几个字符才不同的长名字程序的时候性能是极其差的。幸运的是,符号表算法是一个很容易理解的题目,现在可以期望连接器在处理长名字的时候没有任何问题。
VC6.0中的机制更会复杂,对于函数一般是“?”开始,后跟函数名,使用@符号分割函数所带的参数和所在的namespace,如果参数又是函继续按照上述规则继续,最后的结束符也有一定的规则等等。
DEV-CPP中函数是以“_Z”后面跟上函数名信息,如3fun再跟上参数信息。对于类。模板函数以“_ZN”开头(N表示Namespace)。 [Page]
值得注意的是:C++中Name-mangling都没有把返回值作为函数名中的一项,因此在overload时,不同的返回值,但函数名和参数相同的函数不是重载函数就是这个原因。那你可能会问,为什么不把返回值作为Name-mangling中的一项,我想可能是因为一些函数不允许有返回值(如类的构造函数、析构函数);另外重要的是返回值可能有隐式类型转换,可能会导致一些调用的二义性。当然这些都不是问题,都可以通过一些方法被解决(例如,不允许有返回值的可以加入一个特殊的符号区分),但是在效率和结果的平衡上编译器丢弃了返回值作为Name-mangling中的一项。
编译器中的Name-demangling
当我们知道了C++编译器Name-mangling的方法,我们就可以通过逆过程将其解析还原出原来的函数原型,方便调试。例如在Tornado下,可以通过两种方法将编译后的函数解析出来:
? 可以在(.map文件中)查到对应的编译过后的函数名;
? 在vxwork下通过TCL函数(或者自己写的函数)demangle