一、轧名Name mangling和修饰名 Decoration name
在现代编程语言中,许多情况下需要解析程序实体的唯一既定名称,轧名(Name mangling)(又叫命名修饰)是解决这些问题的一种手段。它在函数、结构、类或者其他数据结构的名称中加入额外的信息编码,因此能从编译器传递更多的语义信息给链接器。
轧名允许编程语言在不同的名称空间(不同的作用域)中为不同的实体提供同名的标识符,而不会有命名的冲突。
编译器产生的目标代码(object code)通常会和其他的目标代码相链接,这通常是由链接器来完成的。链接器需要知道每个程序实体的许多信息。例如,为了成功链接一个函数,需要知道的函数的名字、参数的数目、类型等等。
轧名解决了现代编程语言中的很多问题,比如重载等等。它为连接器提供了额外的信息,在编译器和连接器之间传递信息。
Windows平台的C轧名规则
总之,实际上像C一样不支持重载的语言,并不怎么需要轧名。轧名在C语言中可以为函数提供额外信息,比如调用约定(calling conventions)。例如,Windows平台的编译器支持一系列调用约定,这些约定决定了调用参数是如何被传递给子例程并返回的结果。
为什么有调用约定呢?
汇编语言只提供了一条指令,call ptr,其功能是把CS:IP (指令段:指令指针,决定着下一条执行指令的地址)压栈,并且修改CPU的指令指针,作一个跳转。在函数结束的地方,我们使用另一条指令,ret,其功能是把栈中的返回地址取出,并且跳转到那条指令。
不幸的是,汇编语言只提供了指令跳转的命令,作为函数调用另一个重要组成部分的参数传递,其方式就很五花八门了,你可以通过寄存器传值,可以通过调用栈传值,可以通过某一块具体的内存传值(类似全局变量)。然后在被调用函数中,从寄存器,栈或者是内存中读取这些信息。
想象一下如果被调用函数是某一个程序员所编写的,调用者是另一个程序员,那么他俩之间对于参数的传递方式就有了一个约定。
高级语言的出现,把这个问题隐藏了起来。我们在编写一般的c++程序的时候,通常不需要顾虑参数传递的底层实现,但是,这并不意味着这一问题不再出现——我们只是把责任推给了编译器。编译器作为一个计算机程序,总是遵照一定的规则工作,每一个规则对应了一种调用约定,比如一些已经不再被使用的调用约定__fortran,__pascal...
由于这些调用约定之间并不兼容,编译器利用命名变形来标明具体采用了哪一种调用约定。这套轧名规则是由微软建立的,之后为其他各大编译器厂商包括Digital Mars,Borland, and GNU gcc所遵从。这套规则甚至应用到其他的语言,比如Pascal,D,Delphi,Fortran和C#。这些语言编写的子例程,就以种有异于其默认的约定调用呼叫既有的Windows的类库,反之亦然。
int _cdecl f (int x) { return 0; }
int _stdcall g (int y) { return 0; }
int _fastcall h (int z) { return 0; }
使用32为编译器,轧名后的结果为
_f
_g@4
@h@4
stdcall和fastcall的变形规则里,方法名被编码成_name@X和@name@X,其中X是个十进制数,表示的函数参数列表大小(byte)(包括Fastcall中用寄存器传递的参数),而对于cdecl,方法名只是简单的以下划线开头。