名字修饰

微軟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模組中的識別元必須被進一步名字修飾，因為相同的子程式同可能在不同的模組提供給不同的常式。