前言:

(这部分讲的是历史、由来,不想了解的可跳过)

约在20世纪70年代以前,编译器编译源代码产生目标文件时,符号名与相应的变量和函数的名字是一样的。比如一个汇编源代码里面包含了一个函数foo,那么汇编器将它编译成目标文件以后,foo在目标文件中的相对应的符号名也是foo。当后来UNIX平台和C语言发明时,已经存在了相当多的使用汇编编写的库和目标文件。这样就产生了一个问题,那就是如果一个C程序要使用这些库的话,C语言中不可以使用这些库中定义的函数和变量的名字作为符号名,否则将会跟现有的目标文件冲突。比如有个用汇编编写的库中定义了一个函数叫做main,那么我们在C语言里面就不可以再定义一个main函数或变量了。同样的道理,如果一个C语言的目标文件要用到一个使用Fortran语言编写的目标文件,我们也必须防止它们的名称冲突。

为了防止类似的符号名冲突,UNIX下的C语言就规定,C语言源代码文件中的所有全局的变量和函数经过编译以后,相对应的符号名前加上下划线"_"。而Fortran语言的源代码经过编译以后,所有的符号名前加上"_",后面也加上"_"。比如一个C语言函数"foo",那么它编译后的符号名就是"_foo";如果是Fortran语言,就是"_foo_"。

这种简单而原始的方法的确能够暂时减少多种语言目标文件之间的符号冲突的概率,但还是没有从根本上解决符号冲突的问题。比如同一种语言编写的目标文件还有可能会产生符号冲突,当程序很大时,不同的模块由多个部门(个人)开发,它们之间的命名规范如果不严格,则有可能导致冲突。于是像C++这样的后来设计的语言开始考虑到了这个问题,增加了名称空间(Namespace)的方法来解决多模块的符号冲突问题。

但是随着时间的推移,很多操作系统和编译器被完全重写了好几遍,比如UNIX也分化成了很多种,整个环境发生了很大的变化,上面所提到的跟Fortran和古老的汇编库的符号冲突问题已经不是那么明显了。在现在的Linux下的GCC编译器中,默认情况下已经去掉了在C语言符号前加"_"的这种方式;但是Windows平台下的编译器还保持的这样的传统,比如Visual C++编译器就会在C语言符号前加"_",GCC在Windows平台下的版本(cygwin、mingw)也会加"_"。GCC编译器也可以通过参数选项"-fleading-underscore"或"-fno-leading-underscore"来打开和关闭是否在C语言符号前加上下划线。

C++符号修饰

众所周知,强大而又复杂的C++拥有类、继承、虚机制、重载、名称空间等这些特性,它们使得符号管理更为复杂。最简单的例子,两个相同名字的函数func(int)和func(double),尽管函数名相同,但是参数列表不同,这是C++里面函数重载的最简单的一种情况,那么编译器和链接器在链接过程中如何区分这两个函数呢?为了支持C++这些复杂的特性,人们发明了符号修饰(Name Decoration)或符号改编(Name Mangling)的机制,下面我们来看看C++的符号修饰机制。

首先出现的一个问题是C++允许多个不同参数类型的函数拥有一样的名字,就是所谓的函数重载;另外C++还在语言级别支持名称空间,即允许在不同的名称空间有多个同样名字的符号。比如这段代码:

int func(int);  float func(float);  class C {  int func(int);  class C2 {  int func(int);  };
};  namespace N {  int func(int);  class C {  int func(int);  };
}  

这段代码中有6个同名函数叫func,只不过它们的返回类型和参数及所在的名称空间不同。我们引入一个术语叫做函数签名(Function Signature),函数签名包含了一个函数的信息,包括函数名、它的参数类型、它所在的类和名称空间及其他信息。函数签名用于识别不同的函数,就像签名用于识别不同的人一样,函数的名字只是函数签名的一部分。由于上面6个同名函数的参数类型及所处的类和名称空间不同,我们可以认为它们的函数签名不同。在编译器及链接器处理符号时,它们使用某种名称修饰的方法,使得每个函数签名对应一个修饰后名称(Decorated Name)。编译器在将C++源代码编译成目标文件时,会将函数和变量的名字进行修饰,形成符号名,也就是说,C++的源代码编译后的目标文件中所使用的符号名是相应的函数和变量的修饰后名称。C++编译器和链接器都使用符号来识别和处理函数和变量,所以对于不同函数签名的函数,即使函数名相同,编译器和链接器都认为它们是不同的函数。上面的6个函数签名在GCC编译器下,相对应的修饰后名称如表所示。

函数签名

修饰后名称(符号名)

int func(int)

_Z4funci

float func(float)

_Z4funcf

int C::func(int)

_ZN1C4funcEi

int C::C2::func(int)

_ZN1C2C24funcEi

int N::func(int)

_ZN1N4funcEi

int N::C::func(int)

_ZN1N1C4funcEi

GCC的基本C++名称修饰方法如下:所有的符号都以"_Z"开头,对于嵌套的名字(在名称空间或在类里面的),后面紧跟"N",然后是各个名称空间和类的名字,每个名字前是名字字符串长度,再以"E"结尾。比如N::C::func经过名称修饰以后就是_ZN1N1C4funcE。对于一个函数来说,它的参数列表紧跟在"E"后面,对于int类型来说,就是字母"i"。所以整个N::C::func(int)函数签名经过修饰为_ZN1N1C4funcEi。更为具体的修饰方法我们在这里不详细介绍,有兴趣的读者可以参考GCC的名称修饰标准。幸好这种名称修饰方法我们平时程序开发中也很少手工分析名称修饰问题,所以无须很详细地了解这个过程。binutils里面提供了一个叫"c++filt"的工具可以用来解析被修饰过的名称,比如:

$ c++filt _ZN1N1C4funcEi
N::C::func(int)  

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

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

不同的编译器厂商的名称修饰方法可能不同,所以不同的编译器对于同一个函数签名可能对应不同的修饰后名称。比如上面的函数签名中在Visual C++编译器下,它们的修饰后名称如表所示。

函数签名

修饰后名称

int func(int)

?func@@YAHH@Z

float func(float)

?func@@YAMM@Z

int C::func(int)

?func@C@@AAEHH@Z

int C::C2::func(int)

?func@C2@C@@AAEHH@Z

int N::func(int)

?func@N@@YAHH@Z

int N::C::func(int)

?func@C@N@@AAEHH@Z

我们以int N::C::func(int)这个函数签名来猜测Visual C++的名称修饰规则(当然,你只须大概了解这个修饰规则就可以了)。修饰后名字由"?"开头,接着是函数名由"@"符号结尾的函数名;后面跟着由"@"结尾的类名"C"和名称空间"N",再一个"@"表示函数的名称空间结束;第一个"A"表示函数调用类型为"__cdecl"(函数调用类型我们将在第4章详细介绍),接着是函数的参数类型及返回值,由"@"结束,最后由"Z"结尾。可以看到函数名、参数的类型和名称空间都被加入了修饰后名称,这样编译器和链接器就可以区别同名但不同参数类型或名字空间的函数,而不会导致link的时候函数多重定义。

Visual C++的名称修饰规则并没有对外公开,当然,一般情况下我们也无须了解这套规则,但是有时候可能须要将一个修饰后名字转换成函数签名,比如在链接、调试程序的时候可能会用到。Microsoft提供了一个UnDecorateSymbolName()的API,可以将修饰后名称转换成函数签名。下面这段代码使用UnDecorateSymbolName()将修饰后名称转换成函数签名:

/* 2-4.c
* Compile: cl 2-4.c /link Dbghelp.lib
* Usage: 2-4.exe DecroatedName
*/
#include <Windows.h>
#include <Dbghelp.h>
int main( int argc, char* argv[] )
{
char buffer[256];  if(argc == 2)
{
UnDecorateSymbolName( argv[1], buffer, 256, 0 );
printf( buffer );
}
else
{
printf( "Usage: 2-4.exe DecroatedName\n" );
}  return 0;
}  

C++ 符号修饰和函数签名相关推荐

  1. C++符号修饰与函数签名

    很久以前在一个汇编源代码里包含一个函数foo,那么汇编器将它编译成目标文件以后,foo在目标文件中的相对应的符号名也是foo. 当后来Unix平台和C语言出现后,已经存在了相当多的汇编编写的库和目标文 ...

  2. CLL-符号修饰与函数签名

    我们知道,在c++中,函数是可以重载的,也就是说函数名相同,但参数不同.那么编译器和链接器是怎样区分两个同名的函数的呢?由目标文件的三魂七魄(如果不清楚可以看看这边文章)可以知道,函数名和变量名都是符 ...

  3. c, c++函数名编译符号修饰符说明

    C++ 编译器的函数名修饰规则 函数名字修饰(Decorated Name)方式 函数的名字修饰(Decorated Name)就是编译器在编译期间创建的一个字符串.用来指明函数的定义或原型. LIN ...

  4. C++学习笔记(十)成员变量和成员函数分开存储、this指针、空指针访问成员函数、const修饰成员函数、友元

    1.成员变量和成员函数分开存储 //1.在C++中,类内的成员变量和成员函数分开存储 //2.只有非静态成员变量才属于类的对象上 //空对象占用内存空间为:1 //C++编译器会给每个空对象也分配一个 ...

  5. Go 学习笔记(16)— 函数(02)[函数签名、有名函数、匿名函数、调用匿名函数、匿名函数赋值给变量、匿名函数做回调函数]

    1. 函数签名 函数类型也叫做函数签名,可以使用 fmt.Printf("%T") 格式化参数打印函数类型. package mainimport "fmt"f ...

  6. python3 函数签名简介 验证函数参数

    一个函数或方法,它使用*args和**kwargs作为参数,这样使得它比较通用, 但有时候你想检查传递进来的参数是不是某个你想要的类型.可以使用函数签名来做验证 函数签名对象,表示调用函数的方式,即定 ...

  7. 【Android NDK 开发】JNI 方法解析 ( C/C++ 调用 Java 方法 | 函数签名 | 调用对象方法 | 调用静态方法 )

    文章目录 I . 调用 Java 方法流程 II . 获取 jclass 对象 ( GetObjectClass ) III . 获取 jclass 对象 ( FindClass ) IV . JNI ...

  8. 浅谈python函数签名

    函数签名对象,表示调用函数的方式,即定义了函数的输入和输出. 在Python中,可以使用标准库inspect的一些方法或类,来操作或创建函数签名. 获取函数签名及参数 使用标准库的signature方 ...

  9. inline修饰虚函数问题

    inline修饰虚函数问题 虚函数是否可以内联? 虚函数是否可以内联? 一般来说,inline是编译时的行为,虚函数是在程序执行时的行为,因此编译器一般会拒绝对虚函数进行内联! 该博文为原创文章,未经 ...

最新文章

  1. 仓库码放要求_货物码放规范
  2. 记录网页爬虫注意的几个问题
  3. 使用一阶微分对图像锐化
  4. 蓝桥杯 - 试题 H: 扫雷(思维)
  5. POJ 3087 Shuffle'm Up (模拟+map)
  6. 让redhat5以yum方式安装软件
  7. 中国科学院计算机博士张弛,中科院纳米能源所张弛团队:基于摩擦纳米发电机的自供电无线传感微系统与智能浮标...
  8. python threading.lock
  9. web.xml 总结
  10. python_thrift
  11. Java8中的 Stream 那么彪悍,你知道它的原理是什么吗?
  12. endnote修改正文中参考文献标注_如何在Endnote中修改参考文献格式
  13. 在HTML中什么表示水平线,HTML中加入水平线的标签是( )
  14. http请求中简单的签名验证
  15. JavaScript的prototype是什么?
  16. Predictive and Prescriptive Methods in Operations Research and Machine Learning: An Optimization App
  17. ToDesk软件安装教程(远程办公必备软件)
  18. 服务器千兆网卡芯片,Intel 82574L and 82576 千兆网卡芯片区别.pdf
  19. MSSQL之七 数据完整性
  20. 【HTML5初涉】H5与H4的区别

热门文章

  1. 福禄克FLUKE DSX-CHA003 COAX ADAPTER同轴电缆测试模块DSX-COAX,适用于DSX2-8000 DSX2-5000
  2. FLUKE DSX2-5000CH与DSX2-8000 CH如何测试CAT7类和CAT7A类的网线?
  3. jquery获取所有的兄弟元素
  4. 小程序完全嵌套h5后分享功能的实现
  5. 20171031上海新国际博览中心物流展
  6. SCJ-LD40 雷达水位计(平板雷达水位计)
  7. 乱序和屏障1 : 总览 及 编译器内存屏障
  8. 计算机技术三大支柱,信息技术三大支柱常见七大传感器全解
  9. mysql定义大字段longtxt_mysql中text,longtext,mediumtext等字段类型的意思,以及区别
  10. 梅杰综合征是一种什么疾病?带你深入了解