Name Mangling in C++

摘要:详细介绍了C++中的Name Mangling的原理和gcc中对应的实现,通过程序代码和nm c++filt等工具来验证这些原理。对于详细了解程序的链接过程有一定的帮助

Name Mangling概述

程序的构建过程一般为:各个源文件分别编译,形成目标文件。多个目标文件通过链接器形成最终的可执行程序。显然,从某种程度上说,编译器的输出是链接器的输入,链接器要对编译器的输出做二次加工。从通信的角度看,这两个程序需要一定的协议来规范符号的组织格式。这就是Name Mangling产生的根本原因。
C++的语言特性比C丰富的多,C++支持的函数重载功能是需要Name Mangling技术的最直接的例子。对于重载的函数,不能仅依靠函数名称来区分不同的函数,因为C++中重载函数的区分是建立在以下规则上的:
            函数名字不同 || 参数数量不同||某个参数的类型不同
那么区分函数的时候,应该充分考虑参数数量和参数类型这两种语义信息,这样才能为却分不同的函数保证充分性。
当然,C++还有很多其他的地方需要Name Mangling,如namespace, class, template等等。
总的来说,Name Mangling就是一种规范编译器和链接器之间用于通信的符号表表示方法的协议,其目的在于按照程序的语言规范,使符号具备足够多的语义信息以保证链接过程准确无误的进行。

简单的实验
Name Mangling会带了一个很常见的负面效应,就是C语言的程序调用C++的程序时,会比较棘手。因为C语言中的Name Mangling很简单,不如C++中这么复杂。下面的代码用于演示这两种不同点

/*
 * simple_test.c
 * a demo to show that different name mangling technology in C++ and C 
 */
  
 #include
  
 int rect_area(int x1,int x2,int y1,int y2) 
 {
         return (x2-x1) * (y2-y1);
 }
 
 int elipse_area(int a,int b) 
 {
         return 3.14 * a * b;
 }
 
 int main(int argc,char *argv[])
 {
         int x1 = 10, x2 = 20, y1 = 30,y2 = 40;
         int a = 3,b=4;
         int result1 =rect_area(x1,x2,y1,y2);
         int result2 =elipse_area(a,b);
         return 0;
 }
 
 [name_mangling]$ gcc -c simple_test.c 


 [name_mangling]$ nm simple_test.o 


 0000000000000027 T elipse_area 


 0000000000000051 T main 


 0000000000000000 T rect_area


从上面的输出结果上,可以看到使用gcc编译后对应的符号表中,几乎没有对函数做任何修饰。

接下来使用g++编译:
 
  [name_mangling]$ nm simple_test.o


 0000000000000028 T _Z11elipse_areaii 


 0000000000000000 T _Z9rect_areaiiii 


                                 U __gxx_personality_v0


 0000000000000052 T main
显然,g++编译器对符号的改编比较复杂。所以,如果一个由C语言编译的目标文件中调用了C++中实现的函数,肯定会出错的,因为符号不匹配

简单对_Z9rect_areaiiii做个介绍:
i) C++
语言中规定:以下划线并紧挨着大写字母开头或者以两个下划线开头的标识符都是C++语言中保留的标示符。所以_Z9rect_areaiiii是保留的标识符,g++编译的目标文件中的符号使用_Z开头(C99标准)。
ii)
接下来的部分和网络协议很类似。9表示接下来的要表示的一个字符串对象的长度(现在知道为什么不让用数字作为标识符的开头了吧?)所以rect_area这九个字符就作为函数的名称被识别出来了。
iii) 接下来的每个小写字母表示参数的类型,i表示int类型。小写字母的数量表示函数的参数列表中参数的数量。
所以,在符号中集成了用于区分不同重载函数的足够的语义信息。

如果要在C语言中调用C++中的函数该怎么做?这时候可以使用C++的关键字extern“C”。对应代码如下:

/*
 * simple_test.c
 * a demo to show that different name mangling technology in C++ and C
 */
  
 #include
  
 #ifdef __cplusplus 
 extern "C" { 
 #endif


 int rect_area(int x1,int x2,int y1,int y2) 
 {
         return (x2-x1) * (y2-y1);
 }
  
 int elipse_area(int a,int b) 
 {
         return (int)(3.14 * a * b);
 }
  
 #ifdef __cplusplus 
 }
 #endif
  
 int main(int argc,char *argv[])
 
 {
         int x1 = 10, x2 = 20, y1 = 30,y2 = 40;
         int a = 3,b=4;
         int result1 =rect_area(x1,x2,y1,y2);
         int result2 =elipse_area(a,b);
         return 0;
 }

下面是使用gcc编译的结果:
 
 [name_mangling]$ gcc -c simple_test.c
 
 [name_mangling]$ nm simple_test.o
 
 0000000000000027 T elipse_area
 
 0000000000000051 T main
 
 0000000000000000 T rect_area


再使用g++编译一次: 
 [name_mangling]$ g++ -c simple_test.c
 
 [name_mangling]$ nm simple_test.o
 
                  U __gxx_personality_v0
 
 0000000000000028 T elipse_area
 
 0000000000000052 T main
 
 0000000000000000 T rect_area
可见,使用extern “C”关键字之后,符号按照C语言的格式来组织了。
事实上,C标准库中使用了大量的extern “C”关键字,因为C标准库也是可以用C++编译器编译的,但是要确保编译之后仍然保持C的接口而不是C++的接口(因为是C标准库),所以需要使用extern “C”关键字。

不同编译器使用不同的方式进行name mangling, 你可能会问为什么不将C++ name mangling标准化,这样就能实现各个编译器之间的互操作了。事实上,在C++FAQ列表上有对此问题的回答:
"Compilers differ as to how objects are laid out, how multiple inheritance is implemented, how virtual function calls are handled, and so on, so if the name mangling were made the same, your programs would link against libraries provided from other compilers but then crash when run. For this reason, the ARM(Annotated C++ Reference Manual) encourages compiler writers to make their name mangling different from that of other compilers for the same platform. Incompatible libraries are then detected at link time, rather than at runtime."
编译器由于内部实现的不同而不同,内部实现包括对象在内存中的布局,继承的实现,虚函数调用处理等等。所以如果将name mangling标准化了,不错,你的程序确实能够链接成功,但是运行肯定要崩的。恰恰是因为这个原因,ARM鼓励为同一平台提供的不同编译器应该使用不同的name mangling方式。这样在编译的时候,不兼容的库就会被检测到,而不至于链接时虽然通过了,但是运行时崩溃了。
显然,这是基于运行时崩溃比链接时失败的代价更大这个原则而考虑的。
GCC的name mangling
GCC
采用IA 64name mangling方案,此方案定义于Intel IA64 standard ABI.g++FAQ列表中有以下一段话:
       "GNU C++ does not do name mangling in the same way as other C++ compilers.
This means that object files compiled with one compiler cannot be used with
another”
GNU C++
name mangling方案和其他C++编译器方案不同,所以一种编译器生成的目标文件并不能被另外一种编译器生成的目标文件使用

name demangling
C++
name mangling技术一般使得函数变得面目全非,而很多情况下我们在查看这些符号的时候并不需要看到这些函数name mangling之后的效果,而是想看看是否定义了某个函数,或者是否引用了某个函数,这对于我们调试程序是非常有帮助的。
所以需要一种方法从name mangling之后的符号变换为name mangling之前的符号,这个过程称之为name demangling.事实上有很多工具提供这些功能,最常用的就是c++file命令,c++filt命令接受一个name mangling之后的符号作为输入并输出demangling之后的符号。例如:
 
 [name_mangling]$ c++filt _Z9test_funcRiPKcdSsf
 
 test_func(int&, char const*, double, std::basic_string, std::allocator >, float)


更加详细的介绍,请参考:

http://www.kegel.com/mangle.html


你可能感兴趣的:(C&C++)