当前位置:编程学习 > C/C++ >>

Name Mangling in C++

摘要:详细介绍了C++中的Name Mangling的原理和gcc中对应的实现,通过程序代码和nm c++filt等工具来验证这些原理。对于详细了解程序的链接过程有一定的帮助。
Name Mangling概述
大型程序是通过多个模块构建而成,模块之间的关系由makefile来描述。对于由C++语言编制的大型程序而言,也是符合这个规则。
程序的构建过程一般为:各个源文件分别编译,形成目标文件。多个目标文件通过链接器形成最终的可执行程序。显然,从某种程度上说,编译器的输出是链接器的输入,链接器要对编译器的输出做二次加工。从通信的角度看,这两个程序需要一定的协议来规范符号的组织格式。这就是Name Manglin易做图生的根本原因。
C++的语言特性比C丰富的多,C++支持的函数重载功能是需要Name Mangling技术的最直接的例子。对于重载的函数,不能仅依靠函数名称来区分不同的函数,因为C++中重载函数的区分是建立在以下规则上的:
函数名字不同 || 参数数量不同||某个参数的类型不同
那么区分函数的时候,应该充分考虑参数数量和参数类型这两种语义信息,这样才能为却分不同的函数保证充分性。
当然,C++还有很多其他的地方需要Name Mangling,如namespace, class, template等等。
总的来说,Name Mangling就是一种规范编译器和链接器之间用于通信的符号表表示方法的协议,其目的在于按照程序的语言规范,使符号具备足够多的语义信息以保证链接过程准确无误的进行。
简单的实验
Name Mangling会带了一个很常见的负面效应,就是C语言的程序调用C++的程序时,会比较棘手。因为C语言中的Name Mangling很简单,不如C++中这么复杂。下面的代码用于演示这两种不同点:
 
1. /*
2. * 易做图_test.c
3. * a demo to show that different name mangling technology in C++ and C
4. 
5. * Author: Chaos Lee
6. 
7. */
8.  
9. #include<stdio.h>
10.  
11. int rect_area(int x1,int x2,int y1,int y2)
12. 
13. {
14.         return (x2-x1) * (y2-y1);
15. }
16.  
17. int elipse_area(int a,int b)
18. 
19. {
20.         return 3.14 * a * b;
21. }
22.  
23. int main(int argc,char *argv[])
24. 
25. {
26.         int x1 = 10, x2 = 20, y1 = 30, y2 = 40;
27.         int a = 3,b=4;
28.         int result1 = rect_area(x1,x2,y1,y2);
29.         int result2 = elipse_area(a,b);
30.         return 0;
31. }
 
1. [lichao@sg01 name_mangling]$ gcc -c 易做图_test.c
2. 
3. [lichao@sg01 name_mangling]$ nm 易做图_test.o
4. 
5. 0000000000000027 T elipse_area
6. 
7. 0000000000000051 T main
8. 
9. 0000000000000000 T rect_area
从上面的输出结果上,可以看到使用gcc编译后对应的符号表中,几乎没有对函数做任何修饰。接下来使用g++编译:
 
1.  [lichao@sg01 name_mangling]$ nm 易做图_test.o
2. 0000000000000028 T _Z11elipse_areaii
3. 
4. 0000000000000000 T _Z9rect_areaiiii
5. 
6.                  U __gxx_personality_v0
7. 0000000000000052 T main
显然,g++编译器对符号的改编比较复杂。所以,如果一个由C语言编译的目标文件中调用了C++中实现的函数,肯定会出错的,因为符号不匹配。
简单对_Z9rect_areaiiii做个介绍:
l C++语言中规定 :以下划线并紧挨着大写字母开头或者以两个下划线开头的标识符都是C++语言中保留的标示符。所以_Z9rect_areaiiii是保留的标识符,g++编译的目标文件中的符号使用_Z开头(C99标准)。
l 接下来的部分和网络协议很类似。9表示接下来的要表示的一个字符串对象的长度(现在知道为什么不让用数字作为标识符的开头了吧?)所以rect_area这九个字符就作为函数的名称被识别出来了。
l 接下来的每个小写字母表示参数的类型,i表示int类型。小写字母的数量表示函数的参数列表中参数的数量。
l 所以,在符号中集成了用于区分不同重载函数的足够的语义信息。
如果要在C语言中调用C++中的函数该怎么做?这时候可以使用C++的关键字extern “C”。对应代码如下:
 
1. /*
2. * 易做图_test.c
3. * a demo to show that different name mangling technology in C++ and C
4. 
5. * Author: Chaos Lee
6. 
7. */
8.  
9. #include<stdio.h>
10.  
11. #ifdef __cplusplus
12. 
13. extern "C" {
14. 
15. #endif
16. int rect_area(int x1,int x2,int y1,int y2)
17. 
18. {
19.         return (x2-x1) * (y2-y1);
20. }
21.  
22. int elipse_area(int a,int b)
23. 
24. {
25.         return (int)(3.14 * a * b);
26. }
27.  
28. #ifdef __cplusplus
29. 
30. }
31. #endif
32.  
33. int main(int argc,char *argv[])
34. 
35. {
36.         int x1 = 10, x2 = 20, y1 = 30, y2 = 40;
37.         int a = 3,b=4;
38.         int result1 = rect_area(x1,x2,y1,y2);
39.         int result2 = elipse_area(a,b);
40.         return 0;
41. }
下面是使用gcc编译的结果:
 
1. [lichao@sg01 name_mangling]$ gcc -c 易做图_test.c
2. 
3. [lichao@sg01 name_mangling]$ nm 易做图_test.o
4. 
5. 0000000000000027 T elipse_area
6. 
7. 0000000000000051 T main
8. 
9. 0000000000000000 T rect_area
在使用g++编译一次:
 
1. [lichao@sg01 name_mangling]$ g++ -c 易做图_test.c
2. 
3. [lichao@sg01 name_mangling]$ nm 易做图_test.o
4. 
5.                  U __gxx_personality_v0
6. 
7. 0000000000000028 T elipse_area
8. 
9. 0000000000000052 T main
10. 
11. 0000000000000000 T rect_area
可见,使用extern “C”关键字之后,符号按照C语言的格式来组织了。
事实上,C标准库中使用了大量的extern “C”关键字,因为C标准库也是可以用C++编译器编译的,但是要确保编译之后仍然保持C的接口而不是C++的接口(因为是C标准库),所以需要使用extern “C”关键字。
下面是一个简单的例子:
 
1. /*
2. * libc_test.c
3. * a demo program to show that how the standard C
4. 
5. * library are compiled when encountering a C++ compiler
6. 
7. */
8. #include<stdio.h>
9. int main(int argc,char * argv[])
10. 
11. {
12.         puts("hello world.\n");
13.         return 0;
14. }
搜索一下puts,我们并没有看到extern “C”.奇怪么?
 
1. [lichao@sg01 name_mangling]$ g++ -E libc_test.c | grep 'puts'
2. 
3. extern int fputs (__const char *__restrict __s, FILE *__restrict __stream);
4. 
5. extern int puts (__const char *__s);
6. 
7. extern int fputs_unlocked (__const char *__restrict __s,
8. 
9.  puts("hello world.\n");
搜索一下 extern “C”试下

补充:软件开发 , C++ ,
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,