C++符号修饰Name-mangling

2023-05-16

C++符号修饰

C语言符号修饰

在上古时期,编译器编译源代码产生目标文件时,符号名与相应的变量和函数的名字是一样的。比如一个汇编源代码里面包含一个函数foo,那么汇编器将其编译成目标文件后,foo在目标文件中的相应符号名也是foo。当后来UNIX平台和C语言发明是,已经存在了相当多的使用汇编编写的库和目标文件。这样就产生了一个问题,那就是如果一个C程序要使用这些库的话,C语言中不可以使用这些库中定义的函数和变量的名字作为符号名,否则会与现有的目标文件冲突。

为了防止类似的符号名冲突,UNIX下的C语言就规定,C语言源代码文件中的所有全局变量和函数经过编译以后,相对应的符号名前加上下划线"_"。随着时间的推移,很多操作系统和编译器被完全重写,符号冲突问题不是那么明显了,现在的Linux下的GCC编译器中,默认情况下已经去掉了在C语言符号中加"_"的方式。

C++符号修饰

众所周知,强大而复杂的C++拥有类、继承、虚函数、重载、命名空间等特性,它们使得符号管理更为复杂。为了支持C++这些复杂的特性,人们发明了符号改编(Name Mangling)机制。

GCC的C++名称修饰方法如下:所有符号都以_Z开头,对于在命名空间或者类里面嵌套的名称,后面紧跟N,然后是各个命名空间和类的名字,每个名字前是名字字符串长度,再以E结尾。对于一个函数来说,它的参数列表紧跟在E后面。

签名和名称修饰机制不光被使用到函数上,C++中的全局变量和静态变量也有同样的机制。对于全局变量来说,它和函数一样是一个全局可见的名称,它也遵循上面的名称修饰机制。比如一个名称空间foo中的全局变量bar,它修饰后的名字为:_ZN3foo3barE。需要注意的是,变量的类型并没有被加入到修饰后的名称中,所以无论这个变量是整形还是浮点型,甚至是一个全局对象,它的名称是一样的。

名称修饰机制也被用来防止静态变量的名字冲突。比如main()函数里面有一个静态变量叫foo,而func()函数值里面也有一个静态变量叫foo。为了区分这两个变量,GCC会将它们的符号名称分别修饰为两个不同的名字_ZZ4mainE3foo和_ZZ4funcvE3foo,这样就区分了这两个变量。

//simpleApp.cpp

int foo(int a, int b);
int bar(int a, int b){
    static int val;
    return a + b;
}

int global_var;
int var;

int main(){
    static int static_val;
    int local;
    foo(1,2);
    return 0;
}

编译上述cpp文件,并查看符号表:

[root@xxxx]# gcc -c simpleApp.cpp
[root@xxxx]# readelf -s simpleApp.o

Symbol table '.symtab' contains 15 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS simpleApp.cpp
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
     5: 0000000000000008     4 OBJECT  LOCAL  DEFAULT    4 _ZZ3bariiE3val
     6: 000000000000000c     4 OBJECT  LOCAL  DEFAULT    4 _ZZ4mainE10static_val
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
     9: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
    10: 0000000000000000    20 FUNC    GLOBAL DEFAULT    1 _Z3barii
    11: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    4 global_var
    12: 0000000000000004     4 OBJECT  GLOBAL DEFAULT    4 var
    13: 0000000000000014    26 FUNC    GLOBAL DEFAULT    1 main
    14: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _Z3fooii

存在的问题

由于不同的编译器采用不同的名字修饰方法,必然会导致不同编译器产生的目标文件无法相互链接,这是导致不同编译器之间不能互操作的主要原因之一。

在上面的simpleApp.cpp代码中,我们调用了一个外部的函数foo(int, int),经过C++符号修饰机制后,变成了_Z3fooii。如果我们是从一个外部的静态/动态链接库中找到foo函数,就要求静态/动态链接库中foo函数的命名一致。

如果foo所在的链接库是其它编译器编译的,foo函数可能会被符号修饰机制变为其它的名字,这样链接的时候就无法找到foo函数了。例如,simpleApp.o是在gcc下编译的,而包含foo函数的链接库是在msvc下编译的。

extern “C”

还有一种情况,如果foo函数所在的链接库是以C语言方式编译的,没有经过符号修饰机制。这时候应该怎么处理?我们可以在simpleApp.cpp文件中声明foo函数的地方使用extern "C"关键字,以此声明这部分代码使用C语言的方式进行编译。

//simpleApp.cpp
extern "C"{
    int foo(int a, int b);
}

int bar(int a, int b){
    static int val;
    return a + b;
}

int global_var;
int var;

int main(){
    static int static_val;
    int local;
    foo(1,2);
    return 0;
}

查看编译后的符号表信息::

[root@xxxx]# gcc -c simpleApp.cpp
[root@xxxx]# readelf -s simpleApp.o

Symbol table '.symtab' contains 15 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS simpleApp.cpp
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
     5: 0000000000000008     4 OBJECT  LOCAL  DEFAULT    4 _ZZ3bariiE3val
     6: 000000000000000c     4 OBJECT  LOCAL  DEFAULT    4 _ZZ4mainE10static_val
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
     9: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
    10: 0000000000000000    20 FUNC    GLOBAL DEFAULT    1 _Z3barii
    11: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    4 global_var
    12: 0000000000000004     4 OBJECT  GLOBAL DEFAULT    4 var
    13: 0000000000000014    26 FUNC    GLOBAL DEFAULT    1 main
    14: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND foo

可以看到,加上extern "C"之后,foo函数保持了原来的名字。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++符号修饰Name-mangling 的相关文章

随机推荐