名字修饰

微软Windows上的C语言名字修饰

虽然在不支持函数重载的程序设计语言（例如C语言和经典Pascal语言）中基本上不需要名字修饰，但是它们在一些情况下它们还是用了名字修饰来提供了函数的附加信息。 例如，目标于微软Windows平台的编译器支持许多调用约定。这用于决定哪个参数传入子程序的方式和结果返回的方式。因为不同的调用约定彼此不兼容，所以编译器根据的调用约定重整了链接符号。
名字修饰方案由微软公司首创，现已许多其它的编译器非正式采用，例如Digital Mars公司、Borland公司以及Windows移植版的GNU GCC。该方案甚至被其它语言采用，例如Pascal、D语言、Delphi、Fortran和C#。这允许用这此语言写的子程序使用不同于自身默认的调用约定来调用或被调用于已存在的Windows库。
当编译下列C语言代码的的时候：
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是形参列表的参数中的十进制的字节数，包括用fastcall传入寄存器的。而对于cdecl调用约定，简单地在函数名前加上一条下划线。
注意Windows的64位Microsoft C的调用约定中没有前导下划线。在一些很罕见的地方，这个差异可能导致代码移植到64位上的时候产生无法解析的外部符号。例如Fortran代码可以使用'alias'（别名）来链接到C方法，如下所示：
SUBROUTINE f()!DEC$ ATTRIBUTES C, ALIAS:'_f' :: fEND SUBROUTINE
这在32位平台下编译链接得很好，但是在64位的平台将导致无法解析的外部符号'_f'。一个可行的办法是完全不使用'alias'（典型地，方法名在C语言和Fortran语言中需要大写），或使用BIND选项：
SUBROUTINE f() BIND(C,NAME="f")END SUBROUTINE
Visual Basic 6这样的较老的语言，也需要在声明DLL的输出函数时使用Alias，例如：
Public Declare Function test2 Lib "PackingDLL.dll" Alias "_test2@4" (ByVal param As Integer) As Integer

C++语言的名字修饰

C++编译器是名字修饰使用得出名的编译器。第一个C++编译器的实现是翻译成C语言源代码，以便于让C编译器编译成目标代码。正因如此，符号名必须服从C语言的标识符规则。直至后来，能直接产生机器语言或汇编语言的编译器出现了以后，系统的链接器也是基本上不支持C++的符号的，所以仍然需要名字修饰。
C++语言并没有规定一个标准的名字修饰方式，所以各款编译器都使用各自的名字修饰方式。C++还有一套复杂的语言特性，例如类、模板、命名空间和运算符重载。这改变了基于上下文或用法的特定符号的意义。关于这些特性的元数据能够用粉碎（修饰）调试符号的名字来消除二义性。正因为这些特性的名字修饰系统并没有跨编译器标准化，所以几乎没有链接器可以链接不同编译器产生的目标代码。

简单样例

考虑一个下面的C++程序中的两个f()的定义：
int  f (void) { return 1; }int  f (int)  { return 0; }void g (void) { int i = f(), j = f(0); }
这些是不同的函数，除了函数名相同以外没有任何关系。如果不做任何改变直接把它们当成C代码，结果将导致一个错误——C语言不允许两个函数同名。所以，C++编译器将会把它们的类型信息编码成符号名，结果类似下面的的代码：
int  __f_v (void) { return 1; }int  __f_i (int)  { return 0; }void __g_v (void) { int i = __f_v(), j = __f_i(0); }
注意g()也被名字修饰了，虽然没有任何名字冲突。名字修饰应用于C++的任何符号。

复杂样例

一个更复杂一点的样例，我们考虑一个现实生活中的例子，该例子被GNU GCC 3.x的名字修饰规则实现过。我们看看如何粉碎下列的示例类。粉碎过的符号在各自的标识符名字下面显示。
namespace wikipedia 
{
   class article 
   {
   public:
      std::string format (void); 
         /* = _ZN9wikipedia7article6formatEv */

      bool print_to (std::ostream&); 
         /* = _ZN9wikipedia7article8print_toERSo */

      class wikilink 
      {
      public:
         wikilink (std::string const& name);
            /* = _ZN9wikipedia7article8wikilinkC1ERKSs */
      };
   };}
全部被粉碎过的符号由_Z开头（注意用下划线加大写英文字母是C语言的保留标识符），所以与用户标识符的冲突可以被避免）。嵌套的名字（包括命名空间和类），后面再接一个N，最后一个E。例如wikipedia::article::format将成为：

_ZN·9wikipedia·7article·6format·E  
函数后面接形参的类型信息，例如format()是一个形参为void的函数，于是就接一个v，结果是：

_ZN·9wikipedia·7article·6format·E·v
对于print_to，使用了一个标准类型std::ostream（或更准确地说是std::basic_ostream<char, char_traits<char> >），有着特殊的别名So，所以，这个类型的一个引用类型就是RSo，这个函数的完整名字是：

_ZN·9wikipedia·7article·8print_to·E·RSo

从C++中链接时的C符号的处理

最常见的C++惯常的做法：
#ifdef __cplusplus extern "C" {#endif
    /* ... */#ifdef __cplusplus}#endif
这种写法用于确保下符号是未被C++编译器名字修饰过的——这种代码能使得C++编译器编译出的二进制目标代码中的链接符号是未经过C++名字修饰过的，就像C编译器一样。就像C语言定义是未名字修饰过的一样，C++编译器需要防止名字修饰这些标识符。
例如，C标准字符串库<string.h>通常包含了类似这样子的
#ifdef __cplusplusextern "C" {#endifvoid *memset (void *, int, size_t);char *strcat (char *, const char *);int   strcmp (const char *, const char *);char *strcpy (char *, const char *);#ifdef __cplusplus}#endif
于是，例如这样的代码
if (strcmp(argv[1], "-x") == 0) 
    strcpy(a, argv[2]);else 
    memset (a, 0, sizeof(a));
就能使用正确的、未经名字修饰过的strcmp和memset。如果没有使用extern "C"，那么SunPro C++编译器会产生等价于下面的C代码：
if (__1cGstrcmp6Fpkc1_i_(argv[1], "-x") == 0) 
    __1cGstrcpy6Fpcpkc_0_(a, argv[2]);else 
    __1cGmemset6FpviI_0_ (a, 0, sizeof(a));
而这些链接符号并不存在于C运行库中（例如 libc）。因此将导致链接错误。

C++标准化的名字修饰

大家的共识是标准化的C++名字修饰规则能够在编译器实现之间带来更大的互操作性，但是事实上，这样的标准化自身并不能保证C++编译器的互操作性并且它甚至能制造互操作性是可能的并且是安全的一种错觉。名字修饰仅仅是需要C++实现决定的许多ABI细节之一。其它ABI方面例如异常处理、虚表的设计、结构体和栈帧填充等等 也导致了不同的互不兼容的C++实现。再者，规定一个特定的名字修饰规则会导致在实现限制（例如：符号长度限制）指挥的名字修饰方式的系统上的一些问题。名字修饰的一个标准化的需求也会阻碍不完全不需要名字修饰的实现——例如明白C++语言的链接器。
所以，C++标准并没有尝去标准化名字修饰。相反地，《Annotated C++ Reference Manual》（又叫做ARM， ISBN 0-201-51459-1, 第7.2.1c节）主动提倡使用截然不同的名字修饰方式来防止ABI层面不兼容的链接，例如异常处理和虚表设计。
虽然如此，在一些平台上，全部C++ ABI都被标准化了，包括名字修饰。

C++名字修饰的现实影响

当C++符号从动态链接库和共享对象文件中导出时，名字修饰方式就不再是一个编译器内部的事情的。不同的编译器（或者同一款编译器的不同版本）将产生不同的名字修饰方式的二进制文件。这意味着如果编译器使用了不同方式创建了库和程序经常将导致无法解决的符号。例如，如果一个系统中有多个C++编译器（例如GNU GCC编译器和操作系统供应商的编译器）并且想安装Boost C++ Libraries，那么它需要编译两次——为操作系统供应商的编译器编译一次，为GCC再编译一次。
为了安全目的，产生不兼容的目标代码（基于不同的ABI，例如类和异常）的编译器最好使用不同的名字修饰方式。这保证了这些不兼容性能够在链接的时候被检测出来，而不是一运行软件的时候被发现（这会导致隐藏的bug和严重的稳定性问题）。
正因如此，名字修饰是对于任何C++相关的ABI都是一个要点。

Java的名字修饰

在Java语言中，方法或类的签名包含了它的名字以及它的参数和可适用的返回值类型。签名的格式是有文档说明的，因为Java语言、编译器和.class文件的格式都是全部一起设计的（并且一开始就是面向对象和互操作性的）。

Python的名字修饰

Python语言的名字修饰用于类的“私有”（private）成员。这种类成员的名字由前导双下划线开头，并且后缀下划线不能多于一个。例如__thing将被名字修饰，___thing和__thing_同样也会被名字修饰，但是__thing__和__thing___就不会被名字修饰。Python运行时库不限制访问这些成员，名字修饰只是用来避免拥有同名成员的派生类发生名字冲突。
遇到需要名字修饰的时候，Python把这些名字改成单下划线加上封闭类的名字，例如：
class Test(object):
    def __mangled_name(self):
        pass
    def normal_name(self):
        passprint dir(Test)
将输出：

['_Test__mangled_name', 
'__doc__', 
'__module__', 
'normal_name']

Borland公司的Turbo Pascal/Delphi的名字修饰

为了避免Pascal的名字修饰，可以使用：
exports
  myFunc name 'myFunc',
  myProc name 'myProc';

Free Pascal的名字修饰

Free Pascal支持函数重载和运算符重载，所以它也使用名字修饰来支持这些特性。另外，Free Pascal能够调用由其它语言写的的外部模块定义的符号，也能导出自己的符号供其它语言调用。更多信息，详见Free Pascal Programmer's Guide的子页面Chapter 6.2和Chapter 7.1。

Objective-C的名字修饰

本质上，Objective-C存在两种形式的方法，类方法（静态方法）和实例化方法。Objective-C的方法声明如下：

+ method name: argument name₁:parameter₁ ...
– method name: argument name₁:parameter₁ ...
类方法用+表示，实例化方法用-表示。一个典型的类方法声明是这样子的：

 + (id) initWithX: (int) number andY: (int) number;
 + (id) new;
实例化方法是这样子的：

 – (id) value;
 – (id) setValue: (id) new_value;
这样方法声明都有一个特定的内部表示法。当编译的时候，任何一个方法都会按照下列类方法的方式来命名：

_c_Class_methodname_name₁_name₂_ ...
这是实例化方法：

_i_Class_methodname_name₁_name₂_ ...
Objective-C语法中的冒号被翻译成下划线。所以Objective-C的属于Point类的类方法+ (id) initWithX: (int) number andY: (int) number;将会被翻译成_c_Point_initWithX_andY_，并且实例化方法（属于同一个类） - (id) value;将会被翻译成_i_Point_value。
类的每一种方法都用这种方式标出。但是，为了在全部方法都用这种方式来表示的时候，能够查找到一个类能够回应的方法是很繁琐的。每个方法都赋予了唯一的符号（例如整型）。这样的符号一般叫做选择器。在Objective-C中，选择器可以被直接管理——它们在Objective-C中有特定类型——SEL。
在编译期间，创建了一个把文字表述（例如_i_Point_value）映射到选择器（类型为SEL）的表。管理选择器比操作方法的文字表述更有效。注意一个选择器只能匹配一个方法名，而不是它属于的类——不同的类对同名方法可以有不同的实现。因此，方法的实现也给定了一个特定的标识符——这就叫实现指针，当然也给定了类型IMP。
信息发送由编译器编码，调用id objc_msgSend (id receiver, SEL selector, ...)函数，或者它的表亲，其中receiver是信息的接收者，并且SEL决定需要调用的方法。每个类都有各自的从选择器映射到它们实现的表——实现指针指定实际方法实现的内存地址。类和实现的表是分开的。除了储存在SEL中来用IMP查找表，函数是本质上是匿名的。
选择器的SEL值在类间没有变化。这使得多态成为可能。
Objective-C运行时库负责维护方法的参数和返回值的信息。但是，这信息不是方法名的一部分，不同的类可能有很大的不同。
因为Objective-C不支持命名空间，所以没有必要对类名进行名字修饰（这个在产生的二进制文件中确实发生过）。

Fortran的名字修饰

名字修饰对于Fortran编译器也是必要的，因为原先这个语言是大小写敏感的。随着语言的发展，产生了更多的名字修饰需求，这是因为Fortran 90标准附加的模块和其它特性。名字修饰就成为了需要解决的一个特别常见的问题，因为需要调用来自其它语言（例如C语言）的Fortran库（例如LAPACK）。
由于后来Fortran编译器大小写不敏感，子程序过函数的名字"FOO"必须被转换成典范的大小写方式，而且要由Fortran编译器来格式化，这样它才能无视大小写地用相同方式被链接。不同的编译器用不同的方式来实现了，没有发生过标准化。AIX和HP-UX的Fortran编译器把标识符全转成小写（"foo"），而克雷Unicos的Fortran编译器把标识符全转成大写（"FOO"）。GNU的g77编译器把标识符转成小写后接一个下划线（"foo_"），例外情况是：原先已经有下划线的标识符（"FOO_BAR"）转成后接两个下划线（"foo_bar__"），这是f2c设的约定。许多其它的编译器，包括SGI的IRIX编译器、gfortran和Intel的Fortran编译器，都把标识符全部转成小写后接一个下划线（"foo_"和"foo_bar_"）。
Fortran 90模块中的标识符必须被进一步名字修饰，因为相同的子程序同可能在不同的模块提供给不同的例程。