Loading AI tools
来自维基百科,自由的百科全书
Name mangling,或者Decorated Name,是指程式設計語言中具有儲存性質的對象的名字被編譯器覆寫,以適合編譯器、連結器(linker)、組譯器(assembler)使用[1]。所謂的具有儲存性質的對象,即lvalue對象,是指要實際占用主記憶體空間、有主記憶體位址的那些實體對象,例如:變數(variables)、函式、函式指標等。C++中的純虛擬函式作為特例也屬於這一範疇。而資料類型(data type)就不屬於具有儲存性質的對象。Name mangling如何翻譯成中文,尚無廣泛接受的譯法。可翻譯作改名或名字修飾(name decorating)。
對於支援多載(overload)的程式設計語言,name mangling是必需的因而具有特別重要意義[2]。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行為[3]。
一般情況下,編程者不需要知道C/C++函式的修飾名字。但是,如果在組譯源程式或者行內組譯中參照了C/C++函式,就必須使用其正確的修飾名字[4]。
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協定)[5]. 例如:
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++語言由於含有大量複雜的語言特性,如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等複合資料類型。
<C++的name mangling> ::= ?<Qualified Name> <Type Information>
<Qualified Name> ::= <name>@ [<namespace>@]∞0 @
C++中被mangled的名字都使用問號(?)開始,因為這與用字母數字(alphanumeric)、底線(_)或
@
開頭的C語言程式中的被mangled的名字能完全區分開。
C++中的變數與函式,可定義於名字空間或類中。所以變數與函式受到名字空間或類的限定(qualification)。而名字空間、類又可以巢狀(nest)。
<Qualified Name>
表示變數與函式的名字及所定義的名字空間(或類)的巢狀情況。並採用與C++程式中作用域巢狀相反的順序編碼。例如,namespace1::nestedClass::something
編碼為something@nestedClass@namespace1@@
。
Name mangling時,名字的字串用@
符號作為結束標誌。例如<name>@
,表示<name>
這個字串以@
符號作為結束標誌。因為名字的長度不是事先確定的。如果一個詞法單元的長度是確定的,這些詞法單元就不用@
作為結尾標誌,例如下文中<CV Modifier>
只需用單個字母表示,則無需額外的結束標誌。
<Type Information>
是變數與函式的類型資訊的編碼表示。對於資料對象,就是它的資料類型,見資料對象的name mangling;對於函式,類型資訊就是它的返回值類型、參數類型列表、呼叫協定等情況,見函式的name mangling。
這裡所說的資料對象,包括全域變數(global variables)、類的靜態資料成員變數(static member variables of classes)。
<数据对象的name mangling> ::= ?<name>@[<namespace>@]∞0@<data access modifier>
<data type><CV Modifier>
<data access modifier>
用於表示資料對象的類別,其編碼為:
編碼 | 含義 |
---|---|
0 | Private static member |
1 | Protected static member |
2 | Public static member |
3 | global variable |
4 | static local variable |
<CV Modifier>
是資料對象的訪問屬性的編碼表示,一般常用的值有:A表示default對象、B表示const對象、C表示volatile對象、D表示const volatile對象。詳見小節:<CV Modifier>。需要注意的是對於指標、陣列、參照類型的對象,<CV Modifier>
是對所指向的基本類型的主記憶體空間的訪問屬性。
例如:
int alpha; // mangled as ?alpha@@3HA 其中3表示全局变量,H表示整形,A表示非const非volatile
char beta[6] = "Hello"; // mangled as ?beta@@3PADA
//其中P表示为指针(或一维数组)且指针自身为非const非volatile,
//D表示char类型,两次出现的A都表示基类型为非const非volatile
class myC{
static int s_v; // mangled as ?s_v@myC@@0HA 其中0表示私有静态数据成员
};
函式需要分配主記憶體空間以容納函式的代碼,函式的名字實際上都是lvalue,即指向一塊可執行主記憶體空間的起始位址。而函式模板的實例化(function template instantiation),也是lvalue,需要分配主記憶體空間儲存實例化後的代碼,其name mangling在模板實例化的名字編碼中詳述。
<全局函数的name mangling> ::= ?<function name>@ [<namespace>@]∞0 @ <func modifier>
<calling conv> [<storage ret>] <return type> <parameter type>∞1 <throw type>
<成员函数的name mangling> ::= ?<function name>@ [<namespace>@]∞0 @ <func modifier>
[<const modifier>]<calling conv> [<storage ret>] <return type>
<parameter type>∞1 <throw type>
其中,
<func modifier>
給出了函式是near或far、是否為靜態函式、是類別成員函式還是全域函式、是否為虛擬函式、類別成員函式的訪問級別等基本資訊。需要注意,far屬性僅適用於Windows 16位元環境,32位元或64位元環境下使用扁平(flat)主記憶體位址模型,函式只能具有near屬性。
類別成員函式的<const modifier>
是指是否為唯讀成員函式(constant member function). 如果不是const,則編碼為A;如果是const,則編碼為B;如果是類的靜態成員函式,則省略該項,因為靜態成員函式沒有this指標,無法修改類對象的資料。
<calling conv>
是指函式的呼叫協定。詳見呼叫協定的編碼。常見的呼叫協定的編碼為:__cdecl是A, __pascal是C, __thiscall是E, __stdcall是G, __fastcall是I。在Windows 64位元編譯環境中唯一允許的呼叫協定的編碼是A(詳見64位元程式的呼叫約定)。
[<storage ret>]
是指函式的返回值的是否有const或volatile屬性:
<storage ret> ::= ?<CV Modifier>
如果函式的返回值不具有const或volatile性質,那麼該項在編碼中被省略;但是如果函式的返回類型是class、struct或union等複合資料類型,此項在編碼中是必需的,不能省略。
<return type>
是函式的返回值的資料類型,詳見類型的編碼表示。
<parameter type>
是函式的形參列表(parameter list)的資料類型的編碼。按照形參從左到右順序給每個參數的資料類型編碼,詳見類型的編碼表示。參數類型列表的編碼為:
X
(即函式沒有參數,或者說參數為void
,該編碼也是列表的結束標誌)@
(正常N個形參. 以@
作為列表的結束標誌)Z
(形參表最後一項為...,即ellipsis,其編碼Z
也標誌著列表的結束)<throw type>
是函式丟擲異常的說明,即異常規範(exception specification)。截至Visual C++ 2010,仍是接受但沒有實現異常規範[6]。因此這一項編碼仍然保持為字元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++程式中,需要考慮的具有全域儲存屬性的變數名字及函式的名字。這些名字受namespace、class等作用域(scope)的限定。因此,帶完整限定資訊的名字定義為:
<Qualified Name> ::= <Basic Name> [<Qualifier>]∞0 @
<Basic Name> ::= <Name Fragment> | <Special Name>
<Qualifier> ::= <namespace> | <class name> | <Template Instantiation>
| <Numbered Namespace> | <Back Reference> | <Nested Name>
其中,<Name Fragment>
是組成名字識別碼的ASCII碼串,規定必須以@
作為結尾字尾。<Special Name>
是指建構函式、解構函式、運算子函式(operator function)、虛表(vtable)等內部資料結構等,詳見特殊名字的編碼。
<namespace>与<class name>
就是指C++程式中的名字空間與類。<Template Instantiation>
是指實例化後的函式模板或類別模板,詳見模板實例化的名字編碼。<Numbered Namespace>
是對一個函式內部用花括號{ ... }
給出的不同的作用域(scope)的編號表示,詳見編號名字空間。<Back Reference>
是對一個mangled name的ASCII碼串中重複出現的類型或名字的簡寫表示方法,詳見重複出現的名字與類型的簡寫表示。<Nested Name>
是對靜態局部變數所在的函式名的表示方法,詳見巢狀的名字。
Visual C++的name mangling,有時會用到數(number),例如多維陣列的維數等。數的編碼使用一套獨特的方法:
例如,8編碼為7。29110編碼為BCD@。-1510編碼為?P@。
特殊名字(special names)是指類別的建構函數、解構函式、運算子函式、類的內部資料結構等的名字,表示為字首?
後跟編碼。已知的編碼:
編碼字元 | 不帶底線(_)的含義 | 前置底線(_)的含義 | 前置雙底線(__)的含義 |
---|---|---|---|
0
|
Constructor | operator /= | |
1
|
Destructor | operator %= | |
2
|
operator new | operator >>= | |
3
|
operator delete | operator <<= | |
4
|
operator = | operator &= | |
5
|
operator >> | operator |= | |
6
|
operator << | operator ^= | |
7
|
operator ! | 'vftable' | |
8
|
operator == | 'vbtable' | |
9
|
operator != | 'vcall' | |
A
|
operator[] | 'typeof' | 'managed vector constructor iterator' |
B
|
operator returntype | 'local static guard' | 'managed vector destructor iterator' |
C
|
operator -> | 'string'(Unknown) | 'eh vector copy constructor iterator' |
D
|
operator * | 'vbase destructor' | 'eh vector vbase copy constructor iterator' |
E
|
operator ++ | 'vector deleting destructor' | |
F
|
operator -- | 'default constructor closure' | |
G
|
operator - | 'scalar deleting destructor' | |
H
|
operator + | 'vector constructor iterator' | |
I
|
operator & | 'vector destructor iterator' | |
J
|
operator ->* | 'vector vbase constructor iterator' | |
K
|
operator / | 'virtual displacement map' | |
L
|
operator % | 'eh vector constructor iterator' | |
M
|
operator < | 'eh vector destructor iterator' | |
N
|
operator <= | 'eh vector vbase constructor iterator' | |
O
|
operator > | 'copy constructor closure' | |
P
|
operator >= | 'udt returning' (prefix) | |
Q
|
operator , | Unknown | |
R
|
operator () | RTTI-related code (see below) | |
S
|
operator ~ | 'local vftable' | |
T
|
operator ^ | 'local vftable constructor closure' | |
U
|
operator | | operator new[] | |
V
|
operator && | operator delete[] | |
W
|
operator || | ||
X
|
operator *= | 'placement delete closure' | |
Y
|
operator += | 'placement delete[] closure' | |
Z
|
operator -= |
虛表的mangled name是::= ??_7[ <class name> ]∞0 @6B@
,
字首_P
用在?_PX
之中. 其含義未知。
下表是RTTI相關的編碼,都是在_R
後跟一個數字. 有些編碼還後跟參數.
編碼 | 含義 | 尾部參數 |
---|---|---|
_R0
|
type 'RTTI Type Descriptor' | Data type type. |
_R1
|
'RTTI Base Class Descriptor at (a,b,c,d)' | Four encoded numbers: a, b, c and d. |
_R2
|
'RTTI Base Class Array' | None. |
_R3
|
'RTTI Class Hierarchy Descriptor' | None. |
_R4
|
'RTTI Complete Object Locator' | None. |
函式模板實例化後,就是一個具體的函式。類別模板實例化後,就是一個具體的類資料類型。
<类模板实例化的名字> ::= ?$ <类模板的名字> <模板实参的编码>
<函数模板实例化的名字> ::= ?$ <函数模板的名字> <模板实参的编码>
<函数模板实例化的名字manging> ::= ?$ <函数模板的名字> <模板实参的编码> <函数的类型信息>
模板的名字以字首
?$
開始。?$ <函数模板的名字> <函数模板实参的编码>
可以代替<function name>
,?$ <类模板的名字> <类模板实参的编码>
可以代替<class name>
。
模板實參(template argument),可以分為類型名字(typename)與非類型(non-type)的常數兩類。如果是類型名字或類作為模板實參,那麼其編碼格式詳見類型的編碼表示。如果模板實參是常數(constant),則已知的編碼格式列為下表:
編碼 | 含義 |
---|---|
?x
|
anonymous type template parameter x ('template-parameter-x') |
$0a
|
整數值a |
$2ab
|
實數值a × 10b-k+1, where k是無符號整數a的十進制的位數 |
$Da
|
anonymous type template parameter a ('template-parametera') |
$Fab
|
2-tuple {a,b} (unknown) |
$Gabc
|
3-tuple {a,b,c} (unknown) |
$Hx
|
(unknown) |
$Ixy
|
(unknown) |
$Jxyz
|
(unknown) |
$Qa
|
anonymous non-type template parameter a ('non-type-template-parametera') |
上表中,用a, b, c表示有符號整數,而x, y, z表示無符號整數. 這些有符號整數或無符號整數的編碼格式,詳見數的編碼。上表中,實數值的編碼表示$2ab
,a、b都是有符號整數,但計算實數的值時,實際上規範到以10為基數的科學計數法的表示形式。
例如:
template<class T> class one{
int i;
};
one<int> one1; // mangled as ?one1@@3V?$one@H@@A
//3V...@A表示这是一个全局的类对象,其中的@是类的编码的结束标志;
//类名为one<int>,编码是?$one@H@,其中one@是模板的名字,
//H是模板实参int,其后的@是模板实参表结束标志
class Ce{};
one<Ce> another; /* mangled as ?another@@3V?$one@VCe@@@@A */
//注意,倒数第1个@表示整个(模板实例)类的结束;
// 倒数第2个@表示模板实参表的结束;
// 倒数第3个@表示类Ce的限定情况的结束(此处限定为空,即Ce的作用域是全局);
// 倒数第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;
}
在對一個名字做mangling時,用簡寫方法表示非首次出現的同一個名字或同一個類型。整個簡寫過程需要對ASCII碼串做3次掃描處理:
這適用於函式與函式指標的形參列表。只有編碼超過一個字元的類型參與簡寫,包括指標、函式指標、參照、陣列、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 (int*a, int b, int c, int*d, bool e, bool f, bool*g);
// mangled as ?ExampleFunction@@YA_NPAHHH0_N1PA_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;}
在對碼串完成重複出現的類型的簡寫後,再對結果中所有不同的名字排序編號,從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 T> class tc{
public: void __stdcall func(tc<T>){};
};
int main(int argc, char *argv[])
{
tc<int> ins;
ins.func(ins); /* tc<int>::func(tc<int>) mangled as ?func@?$tc@H@@QAEXV1@@Z */
//其中,?$tc@H@表示实例化的类模板tc<int>,里面的H表示模板实例化的实参是整型
//Q表示public的成员函数;A表示非只读成员函数;E表示thiscall
//X表示函数返回类型void;
//V1@表示形参为一个class,class的名字为''1''号名字,本例中即tc<int>的编码?$tc@H@;
//注意,本例中''0''号名字是函数名,即func@
//最后一个@表示函数的形参表结束;Z表示缺省的exception specification
return 0;
}
模板實例化的名字編碼,基本上就是用模板的名字與模板實參作為一個整體,當作<func name>或<class name>
使用。因此模板實例化的名字在參與完成重複出現的類型簡寫與重複出現的名字簡寫兩步處理之後,再單獨處理模板實例化的模板實參表,對其內部重複出現的名字的編號與簡化。其方法與重複出現的名字簡寫的處理相同。
例1:
template<class T1, class T2> class tc{
int i;
public: void __stdcall func(tc<T1,T2> p1,tc<T1,T2> p2){};
};
class Ce{};
int main(int argc, char *argv[])
{
tc<Ce,Ce> ins;
ins.func(ins,ins); /* void tc<Ce,Ce>::func(tc<Ce,Ce>,tc<Ce,Ce>)
mangled as ?func@?$tc@VCe@@V1@@@QAGXV1@0@Z */
//?$tc@VCe@@V1@@表示实例化的类模板tc<Ce,Ce>,其中的V1@是类模板的实参的重复名字简写;
//函数func的作用域是tc<Ce,Ce>,即tc<Ce,Ce>::func编码为func@?$tc@VCe@@V1@@@ ;
//V1@表示成员函数func的第一个形参为一个类,类名为''1''号名字,本例中即tc<Ce,Ce>,这是重复名字的简写;
//0表示该函数func的第二个形参与形参表中的''0''号形参相同,即tc<Ce,Ce>,这是重复类型的简写
return 0;
}
例2:
class class1{
public: class class2{} ee2;
};
union class2{};
template <class T1, class T2, class T3> void func( T1,T2,T3 ){}
int main(int argc, char *argv[])
{
class1 a;
class2 b;
func<class2, class1::class2, class2>(b,a.ee2,b);
/* func<class2,class1::class2,class2>(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 <class T> void name9 ( T p1){}
int main(int argc, char *argv[])
{
class1 vv;
name9<class1::name9>(vv.ee); /* void name9<class1::name9>(class1::name9)
mangled as ??$name9@V0class1@@@@YAXVname9@class1@@@Z */
// 此例说明模板实例化在做重复名字简化时,
// 模板实参中的name9与模板名字name9相同,因而简化为编号0
return 0;
}
這裡所說的類型,包括資料類型、函式指標的類型、函式模板、類別模板等不需要分配主記憶體空間的一些概念屬性。類型是資料對象與函式這兩類實體的屬性。
編碼 | 不帶底線(_)的含義 | 前置底線(_)的含義 |
---|---|---|
? | 用於表示模板 | |
$ | 用於表示模板 | __w64 (prefix) |
0-9 | Back reference即用於重複出現的類型或名字的簡寫 | |
A | Type modifier (reference) | |
B | Type modifier (volatile reference) | |
C | signed char | |
D | char | __int8 |
E | unsigned char | unsigned __int8 |
F | short | __int16 |
G | unsigned short | unsigned __int16 |
H | int | __int32 |
I | unsigned int | unsigned __int32 |
J | long | __int64 |
K | unsigned long | unsigned __int64 |
L | __int128 | |
M | float | unsigned __int128 |
N | double | bool |
O | long double | Array |
P | Type modifier (pointer) | |
Q | Type modifier (const pointer) | |
R | Type modifier (volatile pointer) | |
S | Type modifier (const volatile pointer) | |
T | union | |
U | struct | |
V | class | |
W | enum | wchar_t |
X | void, Complex Type (coclass) | Complex Type (coclass) |
Y | Complex Type (cointerface) | Complex Type (cointerface) |
Z | ... (ellipsis) |
對於簡單的資料類型,其編碼往往就是一個字母。如int類型編碼為X。對各種衍生的資料類型(如指標)、複合的資料類型(如類)、函式指標、模板等,在下文中分述。
X
表示void
僅當用於表示函式的返回類型、形參表的終止或指標的基本類型, 否則該編碼表示cointerface. 代碼 Z
(表示ellipsis)僅用於表示不定長度的形參列表(varargs).
<指针类型的编码> ::= <type modifier> <CV Modifier> <base type>
<左值引用类型的编码> ::= <type modifier> <CV Modifier> <base type>
<右值指针类型的编码> ::= $$Q <CV Modifier> <base type>
<一维数组类型的编码> ::= <指针类型的编码>
<多维数组类型的编码> ::= <type modifier> <CV Modifier> <Array property><base type>
其中
<type modifier>
作為字首,用於區分各種情況的指標、參照、陣列。指標自身是const還是volatile等訪問屬性,由<type modifier>
確定。共有八種情況:
none | const | volatile | const volatile | |
---|---|---|---|---|
Pointer | P
|
Q
|
R
|
S
|
Reference | A
|
B
|
||
none | ? , $$C
|
<CV Modifier>
表示所指向的基本類型(Referred type)是否具有const或volatile等訪問屬性,詳見小節:<CV Modifier>
。
<base type>
表示指標或參照的基本類型(Referred type),或陣列的成員類型(element type)。其編碼詳見資料類型的編碼表示。
<Array property>
表示多維陣列的基礎維度資訊,其格式為:Y<数组总的维数-1><第2维的长度>...<最后的第N维的长度>
。注意,這裡使用的數字,要用Visual C++ name mangling特有的數字編碼方法,詳見數的編碼。可見,C++語言的陣列是對連續儲存資料的主記憶體的直接隨機訪問(random access)的手段;因此一維陣列視作指標,陣列訪問是否越界,完全由編程者負責;而對多維陣列,必須知道除了第一維之外其它各維的長度,才能做到直接隨機訪問,所以多維陣列作為函式形參時,必須已知其除了第一維之外其它各維的長度(以及總的維數),這些資訊都被編入了陣列的mangled name中。
對於函式指標類型的編碼,其<base type>
為函式呼叫介面資訊,包括使用的呼叫協定、返回值類型、形參類型、允許丟擲的異常等,詳見函式指標類型的編碼。類別成員指標的類型編碼,詳見類別成員指標的類型編碼。類別成員函式指標的類型編碼,詳見類別成員函式指標的類型編碼。
2003年x86-64位元處理器問世後,第一批64位元Windows平台的C++編譯器曾經使用_O
作為陣列類型的字首修飾詞(type modifier). 但不久就改回了32位元Windows平台C++編譯器使用的P
字首.
需注意的是,全域陣列類型被編碼為P(指標型),同時作為函式形參的陣列類型被編碼為Q(常數指標). 這與其本來含義恰恰相反——全域陣列型的變數名字表示某塊主記憶體位址,該名字不能再改為指向其它主記憶體位址;而作為函式形參的陣列型變數的名字所表示的主記憶體位址是可以修改的。但陣列類型這種編碼方法已經被各種C++編譯器廣泛接受。顯然,這是為了與老的代碼保持向下相容。例如:
int ia[10]; //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 int[8] a1; // global array coded as PAH
typedef int[10][8] a2; //global array coded as PAY07H 其中Y标志多维数组,0是(维数-1)即1的编码
// 7是第二维长度8的编码
typedef int[4][16][5] a3; //global array coded as PAY1BA@4H 其中1为(维数-1)即2的编码
//BA@是第二维长度16的编码(16进制的10),4是第3维长度5的编码
int[7][6] // 作为函数形参时,该数据类型编码为 QAY05H
函式指標的類型資訊,包括函式返回類型,函式形參類型,呼叫協定等。以字首P6
開始。各項具體定義可參見函式的類型資訊編碼:
<global function pointer type info> ::= <type modifier> <CV Modifier> <calling conv>
[<storage ret>]<return type>[<parameter type>]∞1<throw type>
一般地,
<type modifier>
取值為P
,意為指標;<CV Modifier>
取值為6,意為指標的基本類型為near屬性的非成員函式。
例如:
typedef const int (__stdcall *FP) (int i); /* coded as P6G?BHH@Z */
// 其中?B表示<storage ret>为const
指向類別成員的指標,其編碼為
<pointer-to-Member Type> = <type modifier> <CV Modifier>
<base-class Qualified Name> <base type>
其中各項的定義詳見指標、參照、陣列的類型編碼。注意,
<CV Modifier>
是指基本類型的屬性,常用的值為: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
類別成員指標(pointer to member)的名字mangling的最末尾處對基本類型訪問屬性的編碼不同於普通的指標,要在最後加上所指向類的帶完整限定資訊的名字:
<pointer-to-member name mangling> ::= ?<Qualified Name> <data access modifier>
<pointer-to-Member Type> <CV Modifier> <base-class Qualified Name>
有的文獻稱[7],類別成員指標、類別成員函式指標的name mangling都必須以Q1@作為結尾,以替代<CV Modifier>
。從下述幾例可以看出,這種說法是錯誤的。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)
}
類別成員函式的指標(pointer to member function),遵從指標類型編碼的一般規則。但與函式指標類型的編碼相比,多了一項<const Modifier>
,表示所指的函式是否為唯讀成員函式(constant member function)。
<pointer-to-member-function type info> ::=
<type modifier> <CV Modifier> <base-class Qualified Name>
[<const Modifier>] <calling conv> [<storage ret>] <return type>
[<parameter type>]∞1 <throw type>
一般地,
<type modifier>
取值為P
,意為指標;<CV Modifier>
取值為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 */
類別成員函式指標(pointer to member function)的名字mangling,對基本類型訪問屬性的編碼<CV Modifier>
不同於普通的指標,要在最後加上所指向類的帶完整限定資訊的名字。
<pointer-to-member name mangling> ::= ?<Qualified Name> <data access modifier>
<pointer-to-Member-Function Type> <CV Modifier> <base-class Qualified Name>
上述定義中,
<CV Modifier>
取值一般是Q
例如:
class xyz{
public: void foo(int) {};
};
void (xyz::*pfunc)(int) ; /* mangled as ?pfunc@@3P8xyz@@AEXH@ZQ1@ */
// 其中Q表示对基类型的访问属性为default;1表示被简写的编号为‘1’的名字,即‘xyz@’;
// 注意,编号为‘0’的名字是‘pfunc@’
<复合类型的编码> ::= <复合类型的种类><复合类型的带限定的名字>
其中複合類型的種類作為字首,union編碼為T, struct編碼為U, class編碼為V, coclass編碼為X, cointerface編碼為Y。複合類型的帶限定的名字<Qualified Name>
,是指按照名字所在的名字空間、所屬的類,逐級列出限定情況(qualifier),詳見名字的編碼。
經常可以看到複合類型的編碼以@@兩個字元結尾,這是因為第一個@表示複合類型名字的字串結束,第二個@表示限定情況的結束(即作用域為全域,限定情況為空)。
編寫代碼時,經常要用到類的前向聲明(forward declaration),即提前聲明這個名字是個類,但類的成員尚未給出。例如:
class myClassName; //mangled type name is VmyClassName@@
class myClassName::embedClassName; //mangled type name is VembedClassName@VmyClassName@@
<枚举类型的编码> ::= W <枚举实际使用的数据类型> <enum-type Qualified Name>
<枚举成员的编码> ::= W <枚举实际使用的数据类型> <enumerator name>@ <enum-type Qualified Name>
其中,W為列舉類型字首詞。
<enum-type Qualified Name>
為列舉類型的帶限定的名字,是指按照名字所在的名字空間、所屬的類,逐級列出限定情況(qualifier),詳見名字的編碼。列舉實際使用的資料類型,
編碼如下:
編碼 | 對應的實際資料類型 |
---|---|
0
|
char |
1
|
unsigned char |
2
|
short |
3
|
unsigned short |
4
|
int (generally normal "enum") |
5
|
unsigned int |
6
|
long |
7
|
unsigned long |
例如:
enum namex:unsigned char {Sunday, Monday}; // enum-type coded as W4namex@@
看起來Visual C++已經把所有列舉類型用int型實現,因此列舉的基本類型(The underlying type of the enumeration identifiers)的編碼總是為4
<CV Modifier>
用於普通的資料對象,表示其是否具有const、volatile等訪問屬性;用於指標、陣列、參照類型,則表明對基本類型的訪問屬性,而指標自身是否為const、volatile等屬性,則專由<type modifier>
編碼表示。
<CV Modifier>
用於函式指標時,表示該指標所指向的基本類型是函式。但與指向資料對象的普通指標不同——函式指標指向的基本類型(即函式)也有自己的主記憶體空間,只是這塊主記憶體空間必定是唯讀的、可執行的,因此函式指標所指向的基本類型主記憶體空間不存在const、volatile等訪問屬性。
<CV Modifier>
的取值情況:
Variable | Function | ||||
---|---|---|---|---|---|
none | const | volatile | const volatile | ||
none | A
|
B , J
|
C , G , K
|
D , H , L
|
6 , 7
|
__based() | M
|
N
|
O
|
P
|
_A , _B
|
Member | Q , U , Y
|
R , V , Z
|
S , W , 0
|
T , X , 1
|
8 , 9
|
__based() Member | 2
|
3
|
4
|
5
|
_C , _D
|
<CV Modifier>
可以有0個或多個字首:
Prefix | Meaning |
---|---|
E
|
type __ptr64 |
F
|
__unaligned type |
I
|
type __restrict |
指標變數的__based()屬性是Microsoft的C++語言擴充. 這一屬性編碼為:
0
(意味著__based(void)
)2<Qualified Name>
(意味著__based(<Qualified Name>)
)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@@表示这是基于<::pBased>的指针;HM21表示是基于“1”的整型指针,
// “1”是重复出现的名字的编号简写,这里就是指pBased@
//
int __based(void) *pbc; // mangled name: ?pbc@@3PM0HM0 其中的0表示这是__based(void).
// 编译器把该变量声明解释为(int __based(void) * __based(void) pbc)
函式的類型資訊,是指呼叫函式時必須考慮的ABI(Application Binary Interface),包括呼叫協定、返回類型、函式形參表、函式丟擲異常的說明(exception specification)等,參見函式的name mangling。
<func modifier>
給出了函式是near或far(但far屬性僅適用於Windows 16位元環境,32位元或64位元環境下只能函式具有near屬性)、是否為靜態函式、是否為虛擬函式、類別成員函式的訪問級別等資訊:
near | far | static near | static far | virtual near | virtual far | thunk near | thunk far | |
---|---|---|---|---|---|---|---|---|
private: | A |
B
|
C |
D
|
E |
F
|
G |
H
|
protected: | I |
J
|
K |
L
|
M |
N
|
O |
P
|
public: | Q |
R
|
S |
T
|
U |
V
|
W |
X
|
not member | Y |
Z
|
上表中的thunk函式[8],是指在多繼承時,由編譯器生成的包裝函式(warpper function),用於多型呼叫實際已被子類對應函式覆蓋(overrided)的父類別虛擬函式,並把指向父類別的this指標調整到指向子類的起始位址。
Code | Exported? | Calling Convention |
---|---|---|
A
|
No | __cdecl |
B
|
Yes | __cdecl |
C
|
No | __pascal __fortran |
D
|
Yes | __pascal |
E
|
No | __thiscall |
F
|
Yes | __thiscall |
G
|
No | __stdcall |
H
|
Yes | __stdcall |
I
|
No | __fastcall |
J
|
Yes | __fastcall |
K
|
No | none |
L
|
Yes | none |
M
|
No | __clrcall |
64位元編程時,唯一可用的呼叫協定的編碼是A
有多種方法,可以方便地檢視一個函式在編譯後的修飾名字[9]:
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)
}
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>(class class1::name9)"
//UnDecorate.cpp
#include <windows.h> //如果不包含此头文件,编译DbgHelp.h时会产生大量语法错误
#include <DbgHelp.h>
#include <tchar.h>
#include <iostream>
#pragma comment(lib,"dbghelp.lib") //告诉链接器使用这个输入库
int _tmain(int argc, _TCHAR* argv[])
{
TCHAR szUndecorateName[256];
memset(szUndecorateName,0,256);
if (2==argc)
{
::UnDecorateSymbolName(argv[1],szUndecorateName,256,0);
std::cout<<szUndecorateName<<std::endl;
}
return 0;
}
編譯後,執行上述程式:
C:\>UnDecorate.exe ?apiname@@YA_NEEPAD@Z
bool __cdecl apiname(unsigned char,unsigned char,char *)
在Windows平台上,使用dllexport關鍵字直接輸出C++函式時,DLL的使用者看到的是修飾後的函式名字[13]. 如果不希望使用複雜的C++修飾後的函式名,替代辦法是在DLL的.def檔案中定義輸出函式的別名,或者把函式聲明為extern "C".
在組譯源程式或者行內組譯中參照了C/C++函式,就必須參照該函式的修飾名字。
Seamless Wikipedia browsing. On steroids.
Every time you click a link to Wikipedia, Wiktionary or Wikiquote in your browser's search results, it will show the modern Wikiwand interface.
Wikiwand extension is a five stars, simple, with minimum permission required to keep your browsing private, safe and transparent.