Visual C++ 名字修飾

Name mangling，或者 Decorated Name，是指程式設計語言中具有儲存性質的物件的名字被編譯器改寫，以適合編譯器、連結器 (linker)、彙編器 (assembler) 使用。所謂的具有儲存性質的物件，即 lvalue 物件，是指要實際佔用記憶體空間、有記憶體地址的那些實體物件，例如：變數 (variables)、函式、函式指標等。C++ 中的純虛擬函式作為特例也屬於這一範疇。而資料型別 (data type) 就不屬於具有儲存性質的物件。Name mangling 如何翻譯成中文，尚無廣泛接受的譯法。可翻譯作改名或名字修飾 (name decorating)。

對於支援過載 (overload) 的程式設計語言，name mangling 是必需的因而具有特別重要意義。C++ 允許函式過載

int foo (int i);

void foo (char c);

C++ 也允許變數名稱在不同限定（qualifier) 下同名：

std::cout;

:cout;

因此 C++ 的編譯器必須給這些函式及變數不同的名字以供程式編譯連結、載入時的內部辨別使用。

C++ 的編譯器可大略分為 Windows 平臺上的 Microsoft Visual C++ 與類 Unix 平臺上的 GNU CC/g++ 兩大類，分別成了各自作業系統環境下的業界工業標準。例如，Windows 平臺上的 Intel C++ Compiler（ICC）與 Digital Mars C++，都與 Visual C++ 保持了二進位制相容 (Application Binary Interface, ABI)。而 Linux 平臺上的 Intel C++ Compiler（ICC）與 HP aC++，都與 GCC 3.x/4.x 做到二進位制相容。GCC 是開源產品，它的內部實現機制是公開的；而 Visual C++ 不公開它內部實現細節，因此在 name mangling 上並無詳盡的正式文件，Visual C++ name mangling 的細節屬於 hacker 行為。

一般情況下，程式設計者不需要知道 C/C++ 函式的修飾名字。但是，如果在彙編源程式或者內聯彙編中引用了 C/C++ 函式，就必須使用其正確的修飾名字。

C 語言的 name mangling

C 語言並不支援過載，因此 C 程式中禁止函式與函式同名，也就沒有必要做 name mangling。但 C 語言的函式呼叫協議 (calling conventions) 五花八門，用某一種呼叫協議編譯的靜態庫或動態庫的函式，如果用另外的呼叫協議去呼叫將會導致錯誤甚至系統崩潰。因此 C 語言編譯器對函式的名字做少量的修飾，用於區別該函式支援哪種呼叫協議，這可以給編譯、連結、特別是庫函式的載入提供額外的檢查資訊。Microsoft C 編譯器在八十年代最先引入這種 mangling 模式，隨後各家編譯器如 Digital Mars, Borland, GNU gcc 等紛紛效仿。

目前，C 語言常用的呼叫協議有三種：cdecl, stdcall 與 fastcall，其它眾多呼叫協議如pascal, fortran, syscall, far 等基本上算是過時了 (obsolote)，因此無需考慮。cdecl 的函式被改名為name；stdcall 的函式被改名為name@X；fastcall 的函式被改名為 @name@X。其中 X 是函式形參所佔用的位元組長度 (包括那些用暫存器傳遞的引數，如 fastcall 協議). 例如：

int cdecl foo (int i); //mangled name is _foo;

int stdcall bar (int j); //mangled name is _bar@4

int fastcall qux (int i) ; //mangled name is @qux@4

注意在 64 位 Windows 平臺上，由於 ABI 有正式標準可依，只存在一種子程式呼叫協議，所以該平臺上的 C 語言的函式不在名字前加上下劃線 (leading underscore). 這會導致一些老的程式 (legacy program), 例如使用 'alias' 去連結 C 語言的函式的 Fortran 程式不能正常工作。

C++ 語言 name mangling 概述

C++ 語言由於含有大量複雜的語言特性，如 classes, templates, namespaces, operator overloading 等，這使得物件的名字在不同使用上下文中具有不同的意義。所以 C++ 的 name mangling 非常複雜。

這些名字被 mangled 的物件，實際上都是具有全域性屬性的儲存物件 (storage object)，其名字將繫結到所佔用記憶體空間的地址，都有 lvalue。儲存物件具體可分為資料變數 (data variable) 與函式 (function)。為什麼不考慮函式的非靜態區域性變數、類的非靜態資料成員呢？因為編譯器把函式的非靜態區域性變數翻譯為 sp + 固定的偏移量；把類的非靜態資料成員翻譯為 this + 固定的偏移量。

下文使用了巴科斯 - 瑙爾正規化 (BNF) 來表述一些 name mangling 的語法定義。方括號表示該項出現 0 次或 1 次，除非在方括號後用上下角標給出該項出現的上下限。下文使用類，一般包含了 class、struct、union 等複合資料型別。

2.1 基本結構

::= ?

::= @ @∞0 @

C++ 中被 mangled 的名字都使用問號 (?) 開始，因為這與用字母數字 (alphanumeric)、下劃線 (_) 或 @開頭的 C 語言程式中的被 mangled 的名字能完全區分開。

C++ 中的變數與函式，可定義於名字空間或類中。所以變數與函式受到名字空間或類的限定 (qualification)。而名字空間、類又可以巢狀 (nest)。

表示變數與函式的名字及所定義的名字空間（或類）的巢狀情況。並採用與 C++ 程式中作用域巢狀相反的順序編碼。例如，namespace1::nestedClass::something 編碼為 something@nestedClass@namespace1@@。

Name mangling 時，名字的字串用 @符號作為結束標誌。例如 @，表示這個字串以 @符號作為結束標誌。因為名字的長度不是事先確定的。如果一個詞法單元的長度是確定的，這些詞法單元就不用 @作為結尾標誌，例如下文中只需用單個字母表示，則無需額外的結束標誌。

是變數與函式的型別資訊的編碼表示。對於資料物件，就是它的資料型別，見資料物件的 name mangling；對於函式，型別資訊就是它的返回值型別、引數型別列表、呼叫協議等情況，見函式的 name mangling。

2.2 資料物件的 name mangling

這裡所說的資料物件，包括全域性變數 (global variables)、類的靜態資料成員變數 (static member variables of classes)。

::= ?@@∞0@

用於表示資料物件的類別，其編碼為：

是資料物件的訪問屬性的編碼表示，一般常用的值有：A 表示 default 物件、B 表示 const 物件、C 表示 volatile 物件、D 表示 const volatile 物件。詳見小節:。需要注意的是對於指標、陣列、引用型別的物件，是對所指向的基型別的記憶體空間的訪問屬性。

例如：

int alpha; //mangled as ?alpha@@3HA 其中 3 表示全域性變數，H 表示整形，A 表示非 const 非 volatile

char beta6 = "Hello"; //mangled as ?beta@@3PADA

// 其中 P 表示為指標 (或一維陣列) 且指標自身為非 const 非 volatile，

//D 表示 char 型別，兩次出現的 A 都表示基型別為非 const 非 volatile

class myC {static int sv; //mangled as ?sv@myC@@0HA 其中 0 表示私有靜態資料成員};

2.3 函式的 name mangling

函式需要分配記憶體空間以容納函式的程式碼，函式的名字實際上都是 lvalue，即指向一塊可執行記憶體空間的起始地址。而函式模板的例項化 (function template instantiation)，也是 lvalue，需要分配記憶體空間儲存例項化後的程式碼，其 name mangling 在模板例項化的名字編碼中詳述。

::= ?@ @∞0 @

∞1

::= ?@ @∞0 @

∞1

其中，給出了函式是 near 或 far、是否為靜態函式、是類成員函式還是全域性函式、是否為虛擬函式、類成員函式的訪問級別等基本資訊。需要注意，far 屬性僅適用於 Windows 16 位環境，32 位或 64 位環境下使用扁平 (flat) 記憶體地址模型，函式只能具有 near 屬性。

類成員函式的是指是否為只讀成員函式 (constant member function). 如果不是 const，則編碼為 A；如果是 const，則編碼為 B；如果是類的靜態成員函式，則省略該項，因為靜態成員函式沒有 this 指標，無法修改類物件的資料。

是指函式的呼叫協議。詳見呼叫協議的編碼。常見的呼叫協議的編碼為：cdecl 是 A, pascal 是 C, thiscall 是 E, stdcall 是 G, fastcall 是 I。在 Windows 64 位編譯環境中唯一允許的呼叫協議的編碼是 A（詳見 64 位程式的呼叫約定）。

是指函式的返回值的是否有 const 或 volatile 屬性：

::= ？

如果函式的返回值不具有 const 或 volatile 性質，那麼該項在編碼中被省略；但是如果函式的返回型別是 class、struct 或 union 等複合資料型別，此項在編碼中是必需的，不能省略。

是函式的返回值的資料型別，詳見型別的編碼表示。

是函式的形參列表 (parameter list) 的資料型別的編碼。按照形參從左到右順序給每個引數的資料型別編碼，詳見型別的編碼表示。引數型別列表的編碼為：

X (即函式沒有引數，或者說引數為 void，該編碼也是列表的結束標誌)
type1 type2 ... typeN @ (正常 N 個形參。以 @作為列表的結束標誌)
type1 type2 ... Z (形參表最後一項為...，即 ellipsis，其編碼 Z 也標誌著列表的結束)

是函式丟擲異常的說明，即異常規範 (exception specification)。截至 Visual C++ 2010，仍是接受但沒有實現異常規範。因此這一項編碼仍然保持為字元 Z。

舉例：

void Function1 (int a, int b); /mangled as ?Function1@@YAXHPAH@Z

其中 Y: 全域性函式

A: cdecl 呼叫協議

X: 返回型別為 void

H: 第一個形參型別為 int

PAH：第二個形參型別為整形指標

@： 形參表結束標誌

Z: 預設的異常規範 */

int Class1::MemberFunction (int a, int b); / mangled in 32-bit mode as

?MemberFunction@Class1@@QAEHHPAH@Z

其中 Q: 類的 public function

A: 成員函式不是 const member function

E: thiscall 呼叫協議

H: 返回值為整形

H: 第一個形參型別為整形

PAH: 第二個形參為整形指標 */

C++ 語言 name mangling 細節

3.1 名字的編碼

C++ 程式中，需要考慮的具有全域性儲存屬性的變數名字及函式的名字。這些名字受 namespace、class 等作用域 (scope) 的限定。因此，帶完整限定資訊的名字定義為：

::= ∞0 @

::=

::=

其中，是組成名字識別符號的 ASCII 碼串，規定必須以 @作為結尾字尾。是指建構函式、解構函式、運算子函式 (operator function)、虛表 (vtable) 等內部資料結構等，詳見特殊名字的編碼。

與就是指 C++ 程式中的名字空間與類。是指例項化後的函式模板或類模板，詳見模板例項化的名字編碼。是對一個函式內部用花括號 {...} 給出的不同的作用域 (scope) 的編號表示，詳見編號名字空間。是對一個 mangled name 的 ASCII 碼串中重複出現的型別或名字的簡寫表示方法，詳見重複出現的名字與型別的簡寫表示。是對靜態區域性變數所在的函式名的表示方法，詳見巢狀的名字。

3.1.1 數的編碼

Visual C++ 的 name mangling，有時會用到數 (number)，例如多維陣列的維數等。數的編碼使用一套獨特的方法：

1-10 編碼為 0-9，這節省了最常用的數的編碼長度；
大於 10 的數編碼為十六進位制，原來的十六進位制數字 0-F 用 A-P 代替，不使用字首 0 或 0x，使用字尾 @作為結束標誌；
0 編碼為 A@；
負數編碼為字首？後跟相應的絕對值 (absolute value) 的編碼。

例如，8 編碼為 7。29110 編碼為 BCD@。-1510 編碼為？P@。

3.1.2 特殊名字的編碼

特殊名字 (special names) 是指類的建構函式、解構函式、運算子函式、類的內部資料結構等的名字，表示為字首？後跟編碼。已知的編碼：

虛表的 mangled name 是::= ??_7 ∞0 @6B@,

字首P 用在？PX 之中。其含義未知。

下表是 RTTI 相關的編碼，都是在_R 後跟一個數字。有些編碼還後跟引數.

3.1.3 模板例項化的名字編碼

函式模板例項化後，就是一個具體的函式。類模板例項化後，就是一個具體的類資料型別。

::= ?$

::= ?$

::= ?$

模板的名字以字首？$ 開始。?$ 可以代替，?$ 可以代替。

模板實參 (template argument)，可以分為型別名字 (typename) 與非型別 (non-type) 的常量兩類。如果是型別名字或類作為模板實參，那麼其編碼格式詳見型別的編碼表示。如果模板實參是常量 (constant)，則已知的編碼格式列為下表：

上表中，用 a, b, c 表示有符號整數，而 x, y, z 表示無符號整數。這些有符號整數或無符號整數的編碼格式，詳見數的編碼。上表中，實數值的編碼表示 $2ab，a、b 都是有符號整數，但計算實數的值時，實際上規範到以 10 為基數的科學計數法的表示形式。

例如：

template class one {int i;};

one one1; //mangled as ?one1@@3V?$one@H@@A

//3V...@A 表示這是一個全域性的類物件，其中的 @是類的編碼的結束標誌；

// 類名為 one，編碼是？$one@H@，其中 one@是模板的名字，

//H 是模板實參 int，其後的 @是模板實參表結束標誌

class Ce ;

one another; / mangled as ?another@@3V?$one@VCe@@@@A /

// 注意，倒數第 1 個 @表示整個 (模板例項) 類的結束；

// 倒數第 2 個 @表示模板實參表的結束；

// 倒數第 3 個 @表示類 Ce 的限定情況的結束 (此處限定為空，即 Ce 的作用域是全域性)；

// 倒數第 4 個 @表示類名碼串的結束

3.1.4 編號名字空間

編號名字空間 (numbered namespace) 用於指出函式靜態區域性變數包含在函式的哪個內部作用域中。之所以需要引入編號名字空間，是為了區分函式內部的不同作用域，從而可以區分包含在不同作用域中但同名的變數，詳見下例。其編碼格式為字首？後跟一個無符號數，無符號數的編碼參見數的編碼小節。

特例情況是，以？A 開始的編號名字空間，是 ('anonymous namespace').

例如：

int func

{

static int i;	//mangled as ?i@?1??func@@YAHXZ@4HA 內部表示為 `func'::`2'::i

	 // ?1 表示第 2 號名字空間；?func@@YAHXZ@是函式的 mangled 名字；4 表示靜態區域性變數

{

static int i; //mangled as ?i@?2??func@@YAHXZ@4HA 內部表示為 `func'::`3'::i

	 // ?3 表示第 3 號名字空間

	}

	return 0;

}

3.1.5 重複出現的名字與型別的簡寫

在對一個名字做 mangling 時，用簡寫方法表示非首次出現的同一個名字或同一個型別。整個簡寫過程需要對 ASCII 碼串做 3 次掃描處理：

對函式或函式指標的形參表中的重複出現的引數資料型別的編碼簡寫；
把第一步獲得結果中重複出現的名字的編碼簡寫；
把第二步獲得的結果中的模板例項化 (模板名字 + 模板實參表) 內部重複出現的名字的編碼簡寫。

3.1.5.1 重複出現的型別的簡寫

這適用於函式與函式指標的形參列表。只有編碼超過一個字元的型別參與簡寫，包括指標、函式指標、引用、陣列、bool、int64、class、例項化的模板類、union、struct、enum 等資料型別。形參表中前 10 種多字元編碼的型別按照出現次序依次編號為 0,1,...,9。用單個字元編碼的型別不參加編號。對不是該資料型別首次出現的形參，用該型別的單個數字的編號代替該資料型別的多個字元的編碼來簡寫表示。排在前十名之後的多字元編碼的資料型別，不再簡寫。函式的返回值型別不參與此編號及簡寫。

如果函式的返回型別或者形參是函式指標型，那麼函式指標型的形參也參與型別排序編號與簡寫，但函式指標型的返回值型別不參與型別排序編號與簡寫。在對型別排序編號時，先編號函式指標型內部的形參的資料型別，再編號函式指標型本身。例如，假如函式的第一個形參是 void (cdecl*)(class alpha, class beta)，那麼 class alpha 編號為 0, class beta 編號為 1, 最後整個函式指標編號為 2.

例如：

bool ExampleFunction (inta, int b, int c, intd, bool e, bool f, bool*g);

//mangled as ?ExampleFunction@@YANPAHHH0N1PA_N@Z

// 其中，_N 為返回型別 bool，不參與型別簡寫，不參與編號排序；

// 型別的排序編號：int 為 0，bool 為 1，bool 為 2。

// int 的編碼為單字元 H，因此不參與編號

// 第 3 個形參不簡寫，仍為 H；第四個形參簡寫為 0，第五個形參簡寫為 1 */

//

typedef int (FP)(int); / 該函式指標型別編碼為 P6APAHPAH@Z */

//

FP	funcfp (int , FP) / mangled as ?funcfp@@YAP6APAHPAH@Z0P6APAH0@Z@Z */

// 其中 P6A 為函式指標型別的編碼字首

// 其中共出現了 5 次 int* (編碼為 PAH)

// 第 1 次為 funcfp 的返回值型別 FP 的返回值型別，不參與排序編號與簡寫；

// 第 2 次為 funcfp 的返回值型別 FP 的形參，參與排序編號，編號為 0，

// 因為是該型別 int * 的首次作為形參出現，不簡寫，仍編碼為 PAH

// 第 3 次為 funcfp 的第一個形參，簡寫為 0；

// 第 4 次為 funcfp 的第二個形參的返回值型別，不參與排序編號與簡寫；

// 第 5 次為 funcfp 的第二個形參的引數，簡寫為 0

{return 0;}

3.1.5.2 重複出現的名字的簡寫

在對碼串完成重複出現的型別的簡寫後，再對結果中所有不同的名字排序編號，從 0 編號到 9。排在前 10 個之後的名字不再編號、簡寫。這裡的名字是指函式、class、struct、union、enum、例項化的帶實參的模板等等的以 @作為結尾字尾的名字。例如，在 alpha@?1beta@@(即 beta::'2'::alpha) 中，0 指代 alpha@, 1 指代 beta@，?1 是編號名字空間‘2’的編碼。特殊名字、編號名字空間的名字都不參加此輪名字的排序編號與簡寫。

例如：

class C1 {class C2 ;

};

union C2 ;

void func (C2,C1::C2) / mangled as ?func@@YAXTC2@@V1C1@@@Z /

// 其中，func 的形參表是 TC2@@V1C1@@@

//TC2@@是第一個形參 union C2；

//V1C1@@是第二個形參 C1::C2，其中 V 表示這是 class，

// 首個 1 表示是編號為 1 的名字 (即 C2@) 重複出現，隨後的 C1@表示 C2 的限定域為 C1

// 隨後的 @表示限定域的巢狀結束。注意，該字串中編號為 “0” 的名字是 func@

對於例項化模板，模板名字後跟模板實參作為一個整體視作一個名字，參加此輪排序編號與簡寫。而模板實參表中的引數序列，單獨處理它的編號及簡寫。例如：

template class tc {public: void stdcall func (tc);};

int main (int argc, char *argv)

{

	tc ins;

	ins.func (ins); / tc::func (tc) mangled as ?func@?$tc@H@@QAEXV1@@Z /

	 // 其中，?$tc@H@表示例項化的類模板 tc，裡面的 H 表示模板例項化的實參是整型

	 //Q 表示 public 的成員函式；A 表示非只讀成員函式；E 表示 thiscall

	 //X 表示函式返回型別 void；

	 //V1@表示形參為一個 class，class 的名字為 1 號名字，本例中即 tc 的編碼？$tc@H@；

	 // 注意，本例中 0 號名字是函式名，即 func@

	 // 最後一個 @表示函式的形參表結束；Z 表示預設的 exception specification

	return 0;

}

3.1.5.3 模板例項化時重複出現的實參的簡寫

模板例項化的名字編碼，基本上就是用模板的名字與模板實參作為一個整體，當作或使用。因此模板例項化的名字在參與完成重複出現的型別簡寫與重複出現的名字簡寫兩步處理之後，再單獨處理模板例項化的模板實參表，對其內部重複出現的名字的編號與簡化。其方法與重複出現的名字簡寫的處理相同。

例 1：

template class tc {

	int i;

public: void stdcall func (tc p1,tc p2);};

class Ce ;

int main (int argc, char *argv)

{

tc ins;

	 ins.func (ins,ins); /* void tc::func (tc,tc)

	 mangled as ?func@?$tc@VCe@@V1@@@QAGXV1@0@Z */

	 //?$tc@VCe@@V1@@表示例項化的類模板 tc，其中的 V1@是類模板的實參的重複名字簡寫；

	 // 函式 func 的作用域是 tc, 即 tc::func 編碼為 func@?$tc@VCe@@V1@@@ ；

//V1@表示成員函式 func 的第一個形參為一個類，類名為 1 號名字，本例中即 tc，這是重複名字的簡寫；

	 //0 表示該函式 func 的第二個形參與形參表中的 0 號形參相同，即 tc，這是重複型別的簡寫

return 0;

}

例 2：

class class1 {public: class class2 ee2;

};

union class2 ;

template void func (T1,T2,T3)

int main (int argc, char *argv)

{

	class1 a;

	class2 b;

	func (b,a.ee2,b);

	 /* func (class2,class1::class2,class2)

	 mangled as ??$func@Tclass2@@V1class1@@T1@@@YAXTclass2@@V0class1@@0@Z */

// 如果不簡寫: ??$func@Tclass2@@Vclass2@class1@@Tclass2@@@@YAXTclass2@@Vclass2@class1@@Tclass2@@@Z

// 首先對函式的形參表中重複的型別簡寫，第 3 個引數與第 1 個引數相同，以型別編號 0 簡寫；

// 然後對整個字串中重複的名字編號、簡寫，第 1 個形參中的名字‘class2@’編號為 0，

// 所以函式第 2 個形參中的‘class2@’被簡寫為 0；

// 這也說明 “函式模板名 + 模板實參”，不作為名字參與編號及簡寫 (但普通函式的名字卻是參與名字編號！)，

// 而類模板名 + 模板實參” 卻算作單獨一個名字而參與編號及簡寫；

// 之後，對模板例項化名字，即 “函式模板名 + 模板實參”，單獨執行重複出現的名字編號及簡寫，

// 其中的‘class2@’出現 3 次，後 2 次被簡寫為 1，這也說明在模板例項化名字內部，

// 僅執行重複名字的簡寫，不執行重複型別的簡寫

	return 0;

}

例 3：

class class1 {public: class name9 ee;

};

template void name9 (T p1)

int main (int argc, char *argv)

{

	class1 vv;

	name9 (vv.ee); /* void name9 (class1::name9)

	 mangled as ??$name9@V0class1@@@@YAXVname9@class1@@@Z */

// 	 此例說明模板例項化在做重複名字簡化時，

// 	 模板實參中的 name9 與模板名字 name9 相同，因而簡化為編號 0

return 0;

}

3.2 資料型別的編碼表示

這裡所說的型別，包括資料型別、函式指標的型別、函式模板、類模板等不需要分配記憶體空間的一些概念屬性。型別是資料物件與函式這兩類實體的屬性。

對於簡單的資料型別，其編碼往往就是一個字母。如 int 型別編碼為 X。對各種衍生的資料型別（如指標）、複合的資料型別（如類）、函式指標、模板等，在下文中分述。

X 表示 void 僅當用於表示函式的返回型別、形參表的終止或指標的基型別，否則該編碼表示 cointerface. 程式碼 Z (表示 ellipsis) 僅用於表示不定長度的形參列表 (varargs).

3.2.1 指標、引用、陣列的型別編碼

::=

::=

::= $$Q

::=

::=

其中作為字首，用於區分各種情況的指標、引用、陣列。指標自身是 const 還是 volatile 等訪問屬性，由確定。共有八種情況：

表示所指向的基型別 (Referred type) 是否具有 const 或 volatile 等訪問屬性，詳見小節：。

表示指標或引用的基型別 (Referred type)，或陣列的成員型別 (element type)。其編碼詳見資料型別的編碼表示。

表示多維陣列的基礎維度資訊，其格式為：Y...。注意，這裡使用的數字，要用 Visual C++ name mangling 特有的數字編碼方法，詳見數的編碼。可見，C++ 語言的陣列是對連續儲存資料的記憶體的直接隨機訪問 (random access) 的手段；因此一維陣列視作指標，陣列訪問是否越界，完全由程式設計者負責；而對多維陣列，必須知道除了第一維之外其它各維的長度，才能做到直接隨機訪問，所以多維陣列作為函式形參時，必須已知其除了第一維之外其它各維的長度（以及總的維數），這些資訊都被編入了陣列的 mangled name 中。

對於函式指標型別的編碼，其為函式呼叫介面資訊，包括使用的呼叫協議、返回值型別、形參型別、允許丟擲的異常等，詳見函式指標型別的編碼。類成員指標的型別編碼，詳見類成員指標的型別編碼。類成員函式指標的型別編碼，詳見類成員函式指標的型別編碼。

2003 年 x86-64 位處理器問世後，第一批 64 位 Windows 平臺的 C++ 編譯器曾經使用_O 作為陣列型別的字首修飾符 (type modifier). 但不久就改回了 32 位 Windows 平臺 C++ 編譯器使用的 P 字首.

需注意的是，全域性陣列型別被編碼為 P (指標型)，同時作為函式形參的陣列型別被編碼為 Q (常量指標). 這與其本來含義恰恰相反 —— 全域性陣列型的變數名字表示某塊記憶體地址，該名字不能再改為指向其它記憶體地址；而作為函式形參的陣列型變數的名字所表示的記憶體地址是可以修改的。但陣列型別這種編碼方法已經被各種 C++ 編譯器廣泛接受。顯然，這是為了與老的程式碼保持向後相容。例如：

int ia10; //ia 是陣列型別的非函式形參的變數

//

int main (int argc, char argv) //argv 是陣列型別的形參，其型別為 (char )

{int j=*(ia++); // 編譯錯誤！ia 是隻讀的 lvalue，不能完成地址的 ++ 操作

char c= (argv++); // 編譯正確！argv 是可以修改的 lvalue

return 0;

}

例如：

typedef int * p1; //coded as PAH 其中 P 表示 default 訪問屬性的指標，

//A 表示對基型別的 default 訪問屬性，H 表示基型別為 int

typedef const int * p2; //coded as PBH 其中 B 表示基型別的 const 訪問屬性

typedef volatile int *p3; //coded as PCH 其中 C 表示基型別的 volatile 訪問屬性

typedef volatile const int *p4; //coded as PDH 其中 D 表示基型別的 const volatile 訪問屬性

typedef int * const p5; //coded as QAH 其中 Q 表示是 const pointer

typedef int * volatile p6; //coded as RAH 其中 R 表示是 volatile pointer

typedef int * const volatile p7; //coded as SAH 其中 S 表示是 const volatile pointer

typedef volatile int * const p6; //coded as QCH 例如這是一個外部 IO 裝置輸入資料的記憶體地址

typedef int &r1; //coded as AAH 其中第一個 A 表示左值引用型別 (l-value reference type)

typedef int&& r2; //coded as $$QAH 其中 $$Q 表示是右值引用型別 (r-value reference type)

typedef const int&& r3; //coded as $$QBH 其中 B 表示基型別是 const 屬性

typedef int8 a1; //global array coded as PAH

typedef int108 a2; //global array coded as PAY07H 其中 Y 標誌多維陣列，0 是 (維數 - 1) 即 1 的編碼

// 7 是第二維長度 8 的編碼

typedef int4165 a3; //global array coded as PAY1BA@4H 其中 1 為 (維數 - 1) 即 2 的編碼

//BA@是第二維長度 16 的編碼 (16 進位制的 10)，4 是第 3 維長度 5 的編碼

int76 // 作為函式形參時，該資料型別編碼為 QAY05H

3.2.2 函式指標型別的編碼

函式指標的型別資訊，包括函式返回型別，函式形參型別，呼叫協議等。以字首 P6 開始。各項具體定義可參見函式的型別資訊編碼：

::=

∞1

一般地，取值為 P，意為指標；取值為 6，意為指標的基型別為 near 屬性的非成員函式。

例如：

typedef const int (stdcall FP) (int i); / coded as P6G?BHH@Z */

// 其中？B 表示為 const

3.2.3 類成員指標的型別編碼

指向類成員的指標，其編碼為

=

其中各項的定義詳見指標、引用、陣列的型別編碼。注意，是指基型別的屬性，常用的值為：Q for default， R for const; S for volatile; T for const volatile。

例如：

class C1 {int i;};

typedef int C1::*p; //coded as PQC1@@H

3.2.4 類成員指標變數的 mangled name

類成員指標 (pointer to member) 的名字 mangling 的最末尾處對基型別訪問屬性的編碼不同於普通的指標，要在最後加上所指向類的帶完整限定資訊的名字：

::= ?

有的文獻稱，類成員指標、類成員函式指標的 name mangling 都必須以 Q1@作為結尾，以替代。從下述幾例可以看出，這種說法是錯誤的。Q 是對所指向的成員型別使用 default 訪問屬性，這是最常見的情況。1 是該指標所指向類的名字簡寫，因為在此位置之前該類的名字必然已經出現在該資料型別的編碼中，所以此處名字的簡寫是必然的。但不一定總是簡寫作 1

下例中，成員指標變數的 mangled name 以 S12@結尾：

class outer {

public:

	class cde {public: volatile int i;};

};

volatile int outer::cde::* p; //mangled as ?p@@3PScde@outer@@HS12@

// 其中兩個 S 都是表示基型別為 volatile 屬性

// 1 是 cde@的簡寫

// 2 是 outer@的簡寫

例 2：

class C1 {public: int i;};

C1 const *pi; // ?pi@@3PBVC1@@B 一個簡單的指標變數。作為對比

typedef int C1::* TP; //coded as PQC1@@H

void func (TP){

static TP ppp=0; // `func'::`2'::ppp mangled as ?ppp@?1??func@@YAXPQC1@@H@Z@4PQ2@HQ2@

// 其中，?ppp@?1??func@@YAXPQC1@@H@Z@表示帶作用域限定資訊的名字 `func'::`2'::ppp

//4 表示靜態區域性變數；PQ2@H 表示成員指標型別，其中的 “2” 是 C1@的簡寫。

// 注意 ppp@編號為 0，func@編號為 1

// 最後三個字元 Q2@表示對基型別 “2”(C1@的簡寫) 的訪問屬性為 Q (預設屬性，即非 const 非 volatile)

}

3.2.5 類成員函式指標的型別編碼

類成員函式的指標 (pointer to member function)，遵從指標型別編碼的一般規則。但與函式指標型別的編碼相比，多了一項，表示所指的函式是否為只讀成員函式 (constant member function)。

::=

∞1

一般地，取值為 P，意為指標；取值為 8，意為指標的基型別為 near 屬性的類成員函式。其它各項取值參見函式的 name mangling。

例如：

class C1 {public: void foo (int) const

;};

typedef void (C1::TP)(int) const; / coded as P8C1@@BEXH@Z

其中 B 表示 const member function; E 表示 thiscall */

3.2.6 類成員函式的指標變數的 mangled name

類成員函式指標 (pointer to member function) 的名字 mangling，對基型別訪問屬性的編碼不同於普通的指標，要在最後加上所指向類的帶完整限定資訊的名字。

::= ?

上述定義中，取值一般是 Q

例如：

class xyz {public: void foo (int) ;};

void (xyz::pfunc)(int) ; / mangled as ?pfunc@@3P8xyz@@AEXH@ZQ1@ */

// 其中 Q 表示對基型別的訪問屬性為 default；1 表示被簡寫的編號為‘1’的名字，即‘xyz@’；

// 注意，編號為‘0’的名字是‘pfunc@’

3.2.7 複合型別 (union, struct, class, coclass, cointerface) 的編碼

::=

其中複合型別的種類作為字首，union 編碼為 T, struct 編碼為 U, class 編碼為 V, coclass 編碼為 X, cointerface 編碼為 Y。複合型別的帶限定的名字，是指按照名字所在的名字空間、所屬的類，逐級列出限定情況 (qualifier)，詳見名字的編碼。

經常可以看到複合型別的編碼以 @@兩個字元結尾，這是因為第一個 @表示複合型別名字的字串結束，第二個 @表示限定情況的結束 (即作用域為全域性，限定情況為空)。

編寫程式碼時，經常要用到類的前向宣告 (forward declaration)，即提前宣告這個名字是個類，但類的成員尚未給出。例如:

class myClassName; //mangled type name is VmyClassName@@

class myClassName::embedClassName; //mangled type name is VembedClassName@VmyClassName@@

3.2.8 列舉型別 (enum) 的編碼

::= W

::= W @

其中，W 為列舉型別字首詞。為列舉型別的帶限定的名字，是指按照名字所在的名字空間、所屬的類，逐級列出限定情況 (qualifier)，詳見名字的編碼。列舉實際使用的資料型別，

編碼如下：

例如：

enum namex:unsigned char {Sunday, Monday}; //enum-type coded as W4namex@@

看起來 Visual C++ 已經把所有列舉型別用 int 型實現，因此列舉的基型別 (The underlying type of the enumeration identifiers) 的編碼總是為 4

3.2.8.1

用於普通的資料物件，表示其是否具有 const、volatile 等訪問屬性；用於指標、陣列、引用型別，則表明對基型別的訪問屬性，而指標自身是否為 const、volatile 等屬性，則專由編碼表示。

用於函式指標時，表示該指標所指向的基型別是函式。但與指向資料物件的普通指標不同 —— 函式指標指向的基型別（即函式）也有自己的記憶體空間，只是這塊記憶體空間必定是隻讀的、可執行的，因此函式指標所指向的基型別記憶體空間不存在 const、volatile 等訪問屬性。

的取值情況：

可以有 0 個或多個字首:

3.2.8.2 based 屬性的變數

指標變數的based 屬性是 Microsoft 的 C++ 語言擴充套件。這一屬性編碼為:

0 (意味著based (void))
2 (意味著based )
5 (意味著沒有based )

例如：

int *pBased; //mangled name: ?pBased@@3PAHA

int based (pBased) * pBasedPtr; // 需要注意 Visual C++ 編譯器把這個指標變數的宣告解釋為：

// (int based (pBased) * based (pBased) pBasedPtr)

// 因此其 mangled name: ?pBasedPtr@@3PM2pBased@@HM21@

// 其中 PM2pBased@@表示這是基於的指標；HM21 表示是基於 “1” 的整型指標，

// “1” 是重複出現的名字的編號簡寫，這裡就是指 pBased@

//

int based (void) *pbc; //mangled name: ?pbc@@3PM0HM0 其中的 0 表示這是based (void).

// 編譯器把該變數宣告解釋為 (int based (void) * based (void) pbc)

3.3 函式的型別資訊編碼

函式的型別資訊，是指呼叫函式時必須考慮的 ABI (Application Binary Interface)，包括呼叫協議、返回型別、函式形參表、函式丟擲異常的說明 (exception specification) 等，參見函式的 name mangling。

3.3.1

給出了函式是 near 或 far（但 far 屬性僅適用於 Windows 16 位環境，32 位或 64 位環境下只能函式具有 near 屬性）、是否為靜態函式、是否為虛擬函式、類成員函式的訪問級別等資訊：

上表中的 thunk 函式，是指在多繼承時，由編譯器生成的包裝函式 (warpper function)，用於多型呼叫實際已被子類對應函式覆蓋 (overrided) 的父類虛擬函式，並把指向父類的 this 指標調整到指向子類的起始地址。

3.3.1 呼叫協議的編碼

64 位程式設計時，唯一可用的呼叫協議的編碼是 A

檢視 Visual C++ 的函式的修飾後的名字

有多種方法，可以方便地檢視一個函式在編譯後的修飾名字：

直接用工具軟體 (如微軟開發環境提供的 dumpbin) 檢視 obj、exe 等二進位制檔案。使用 dumplib 檢視.obj 或.lib 檔案時，使用 "/SYMBOLS" 命令列選項。
編譯時使用 "/FAu" 編譯選項，生成帶有豐富註釋資訊的彙編源程式，其副檔名是.cod 或者.asm，可以檢視每個 C/C++ 函式的修飾名字。
在源程式中使用微軟提供的預定義宏 (Microsoft-Specific Predefined Macros)—— FUNCDNAME，例如：

void exampleFunction

{printf ("Function name: % s\n", FUNCTION);

printf ("Decorated function name: % s\n", FUNCDNAME);

printf ("Function signature: % s\n", FUNCSIG);

// 輸出為：

//-------------------------------------------------

// Function name: exampleFunction

// Decorated function name: ?exampleFunction@@YAXXZ

// Function signature: void cdecl exampleFunction (void)

}

由修飾名字反查其未修飾時的原名

使用微軟 Visual C++ 中的解析修飾名字的工具軟體 undname.exe。例如：

C:\>undname.exe ??$name9@V0class1@@@@YAXVname9@class1@@@Z

Microsoft (R) C++ Name Undecorator

Copyright (C) Microsoft Corporation. All rights reserved.

Undecoration of :- "??$name9@V0class1@@@@YAXVname9@class1@@@Z"

is :- "void cdecl name9 (class class1::name9)"

使用 Windows 提供的系統呼叫 UnDecorateSymbolName 把修飾名字翻譯為未修飾名字。UnDecorateSymbolName 在 DbgHelp.h 或 imagehlp.h 中宣告，在 DbgHelp.dll 中實現，需要使用匯入庫 DbgHelp.lib。Windows SDK 中包含了 DbgHelp.h 與 DbgHelp.lib。示例程式：

//UnDecorate.cpp

include // 如果不包含此標頭檔案，編譯 DbgHelp.h 時會產生大量語法錯誤
include
pragma comment (lib,"dbghelp.lib") // 告訴連結器使用這個輸入庫

int tmain (int argc, TCHAR* argv)

{

	TCHAR szUndecorateName256;

	memset (szUndecorateName,0,256);

	if (2==argc)

	{::UnDecorateSymbolName (argv1,szUndecorateName,256,0);

		std::cout

編譯後，執行上述程式：

C:\>UnDecorate.exe ?apiname@@YA_NEEPAD@Z

bool cdecl apiname (unsigned char,unsigned char,char *)

C++ 修飾名字的用途

6.1 DLL 輸出的 C++ 函式

在 Windows 平臺上，使用 dllexport 關鍵字直接輸出 C++ 函式時，DLL 的使用者看到的是修飾後的函式名字。如果不希望使用複雜的 C++ 修飾後的函式名，替代辦法是在 DLL 的.def 檔案中定義輸出函式的別名，或者把函式宣告為 extern "C".

6.2 在彙編源程式或者內聯彙編中引用 C/C++ 函式

在彙編源程式或者內聯彙編中引用了 C/C++ 函式，就必須引用該函式的修飾名字。