Visual C++名字修飾
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語言的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協議)[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++語言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等複合數據類型。
基本結構
<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。
數據對象的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表示私有静态数据成员
};
函數的name mangling
函數需要分配內存空間以容納函數的代碼,函數的名字實際上都是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
,該編碼也是列表的結束標誌)- type1 type2 ... typeN
@
(正常N個形參. 以@
作為列表的結束標誌) - type1 type2 ...
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++語言name mangling細節
名字的編碼
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),例如多維數組的維數等。數的編碼使用一套獨特的方法:
- 1-10 編碼為 0-9,這節省了最常用的數的編碼長度;
- 大於10的數編碼為十六進制,原來的十六進制數字0-F用A-P代替,不使用前綴0或0x,使用後綴@作為結束標誌;
- 0編碼為A@;
- 負數編碼為前綴?後跟相應的絕對值(absolute value)的編碼。
例如,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
類成員指針變量的mangled name
類成員指針(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 */
類成員函數的指針變量的mangled name
類成員函數指針(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, struct, class, coclass, cointerface)的編碼
<复合类型的编码> ::= <复合类型的种类><复合类型的带限定的名字>
其中複合類型的種類作為前綴,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@@
枚舉類型(enum)的編碼
<枚举类型的编码> ::= 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>
<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()屬性的變量
指針變量的__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>
<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
查看Visual C++的函數的修飾後的名字
有多種方法,可以方便地查看一個函數在編譯後的修飾名字[9]:
- 直接用工具軟件(如微軟開發環境提供的dumpbin)查看obj、exe等二進制文件。使用dumplib查看.obj或.lib文件時,使用"/SYMBOLS"命令行選項。[10]
- 編譯時使用"/FA[c|s|u]"編譯選項,生成帶有豐富注釋信息的匯編源程序,其文件擴展名是.cod或者.asm,可以查看每個C/C++函數的修飾名字[11]。
- 在源程序中使用微軟提供的預定義宏(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>(class class1::name9)"
- 使用Windows提供的系統調用UnDecorateSymbolName()[12]把修飾名字翻譯為未修飾名字。UnDecorateSymbolName在DbgHelp.h或imagehlp.h中聲明,在DbgHelp.dll中實現,需要使用導入庫DbgHelp.lib。Windows SDK中包含了DbgHelp.h與DbgHelp.lib。示例程序:
//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 *)
C++修飾名字的用途
DLL輸出的C++函數
在Windows平台上,使用dllexport關鍵字直接輸出C++函數時,DLL的用戶看到的是修飾後的函數名字[13]. 如果不希望使用複雜的C++修飾後的函數名,替代辦法是在DLL的.def文件中定義輸出函數的別名,或者把函數聲明為extern "C".
在匯編源程序或者內聯匯編中引用C/C++函數
在匯編源程序或者內聯匯編中引用了C/C++函數,就必須引用該函數的修飾名字。
參考文獻
- ^ 微软MSDN的定义是:“A decorated name is a string created by the compiler during compilation of the function definition or prototype.”. [2012-08-11]. (原始內容存檔於2016-10-10).
- ^ 微软MSDN的《Using Decorated Names》:"You must specify the decorated name of C++ functions that are overloaded ... ..., in order for LINK and other tools to be able to match the name. ". [2012-08-11]. (原始內容存檔於2016-05-06).
- ^ 微軟MSDN的《Format of a C++ Decorated Name》
- ^ 微软MSDN的《Using Decorated Names》:"You must also use decorated names in assembly source files that reference a C or C++ function name.". [2012-08-11]. (原始內容存檔於2016-05-06).
- ^ 微软MSDN的《Format of a C Decorated Name》. [2012-08-11]. (原始內容存檔於2016-04-02).
- ^ 參見VC++ Compiler Warning C4290
- ^ Calling conventions for different C++ compilers (頁面存檔備份,存於網際網路檔案館) pp29:"This code is replaced by Q1@ for member pointers and member function pointers, regardless of storage class."
- ^ 參見Thunk (object-oriented programming)
- ^ 微軟MSDN的《Viewing Decorated Names》
- ^ 微軟MSDN的《Using DUMPBIN to View Decorated Names》
- ^ 微软MSDN的《Using a Listing to View Decorated Names》. [2012-08-11]. (原始內容存檔於2016-05-09).
- ^ MSDN的幫助文章《UnDecorateSymbolName function》
- ^ 微软的MSDN关于"dllexport, dllimport"的帮助文章. [2012-07-29]. (原始內容存檔於2012-07-09).