摘要

在C中,Name Mangling 是为了支持重载而加入的一项技术,目前C Name Mangling 并没有统一的标准,也没有较完整的中文文档化资料,所以本篇文章在VS2005环境中,解析C++ Name Mangling 的技术细节,以及怎样将VC Name Mangling后的名称还原为可读的形式。

Name Mangling 简介

Name Mangling 是一种在编译过程中,将函数、变量的名称重新改编的机制。在 C重载、namespace等操作符下,函数可以有同样的名字,编译器为了区分各个不同地方的函数,将各个函数通过编译器内定的算法,将函数改成唯一的名称。
Name Mangling翻译成中文意思为:名字修饰、名字改编等,由于对这个翻译没有统一的约定,所以本文中采用英文表示。
在写VC程序时,我们有时会遇到类似于“error LNK2019: unresolved external symbol “void __cdecl MyFun (void)” (?MyFun@@YAXXZ) ) referenced in function _wmain”的连接错误,此语句中“?MyFun@@YAXXZ”是VC Name Mangling后的结果。本文主要讨论Name Mangling 后名称还原为可读的方式。
本文首先讨论 VC 环境中,C/C++ 语言的 Name Mangling 算法机制。然后讨论手动将 C++ 语言 Name Mangling 后的字符串转换为函数的定义式,最后编码实现还原。

  1. VC环境中Name Mangling
    在VC中,微软采用了自己独特的Name Mangling技术,当然微软也为此Name Mangling技术申请了专利。
    想要查看 VC 将函数名称 Name Mangling 的结果,只需将函数声明,不实现,然后调用之即可。
    下面先讨论VC环境中C语言的Name Mangling技术,然后再讨论C++。

  2. VC环境中C 语言的 Name Mangling
    在VC中,也可以采用C语言编译器,只需要如下设置:Project ->Property ->Configuration Properties -> C/C++ -> Advanced Compile As,将其设为“(/TC)”即可
    在 C 语言中,函数可以有如下声明方式(其中 CALLTYPE 可以为 cdecl、stdcall、fastcall等)

1、void CALLTYPE fun();
2、int
CALLTYPE fun(int); ;

假设此处 CALLTYPE 为 cdecl(即:#define CALLTYPE __cdecl),Name Mangling 结果

1、_fun
2、_fun

看看其他调用方式,如:stdcall(#define CALLTYPE __stdcall),结果如下:

1、_fun@0
2、_fun@4

fastcall 的结果(#define CALLTYPE __ fastcall):
1、@fun@0
2、@fun@4

由此,我们可以得出结论,从被 Name Mangling 后的字符串推断出原来的函数名。
1、cdecl
在此声明方式下,仅仅在函数名前加一个下划线,至于函数返回值、参数,完全没有处理。
2、
stdcall
在此声明方式下,在函数名前加一个下划线,然后紧跟“@”符号,最后是函数参数大小总和(注意:此总和包含了字节填充)。
3、fastcall
在此声明方式下,跟 stdcall 唯一不同的是,函数前面的下划线变为了“@”符号。

由上面 5 个实例函数,我们大概可以看到 VC 环境中,C 语言 Name Mangling 技术了,但也可以发现,从 Name Mangling 后的字符串,并不能得出函数原来的定义式。不同的定义式,Name Mangling 后的名称可以相同,由此也可以知道,C 语言不支持函数重载。

Name mangling或者Decorated Name是指程序设计语言中具有存储性质的对象的名字被编译器改写,以适合编译器、链接器(linker)、汇编器(assembler)使用[1]。 所谓的具有存储性质的对象,即lvalue对象,是指要实际占用内存空间、有内存地址的那些实体对象,例如:变量(variables)、函数、函数指针 等。C中的纯虚函数作为特例也属于这一范畴。而数据类型(data type)就不属于具有存储性质的对象。
对于支持重载(overload)的程序设计语言,name mangling是必需的因而具有特别重要意义。C允许函数重载
int foo(int i);
void foo(char c);
C++也允许变量名称在不同限定(qualifier)下同名:
std::cout;
::cout;

因此C的编译器必须给这些函数及变量不同的名字以供程序编译链接、载入时的内部辨别使用。
C的编译器可大略分为Windows平台上的Microsoft Visual C与类Linux平台上的GNU GCC/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语言编译 器对函数的名字做少量的修饰,用于区别该函数支持哪种调用协议,这可以给编译、链接、特别是库函数的载入提供额外的检查信息。

目前,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等复合数据类型。
基本结构

::= ?
::= @ [@]∞0 @

C中被mangled的名字都使用问号(?)开始,因为这与用字母数字(alphanumeric)、下划线(_)或@开头的C语言程序中的被mangled的名字能完全区分开。
C中的变量与函数,可定义于名字空间或类中。所以变量与函数受到名字空间或类的限定(qualification)。而名字空间、类又可以嵌套(nest)。 表示变量与函数的名字及所定义的名字空间(或类)的嵌套情况。并采用与C++程序中作用域嵌套相反的顺序编码。例如,namespace1::nestedClass::something编码为something@nestedClass@namespace1@@。
Name mangling时,名字的字符串用@符号作为结束标志。例如@,表示这个字符串以@符号作为结束标志。因为名字的长度不是事先确定的。如果一个词法单元的长度是确定的,这些词法单元就不用@作为结尾标志,例如下文中只需用单个字母表示,则无需额外的结束标志。
是变量与函数的类型信息的编码表示。对于数据对象,就是它的数据类型,见数据对象的name mangling;对于函数,类型信息就是它的返回值类型、参数类型列表、调用协议等情况,见函数的name mangling。

数据对象的name mangling

这里所说的数据对象,包括全局变量(global variables)、类的静态数据成员变量(static member variables of classes)。
<数据对象的name mangling> ::= ?@[@]∞0@

用于表示数据对象的类别,其编码为:
编码 含义
0 Private static member
1 Protected static member
2 Public static member
3 global variable
4 static local variable
是数据对象的访问属性的编码表示,一般常用的值有:A表示default对象、B表示const对象、C表示volatile对象、D表示const volatile对象。详见小节:。需要注意的是对于指针、数组、引用类型的对象,是对所指向的基类型的内存空间的访问属性。
例如:
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> ::= ?@ [@]∞0 @
[] ∞1
<成员函数的name mangling> ::= ?@ [@]∞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。
[]是指函数的返回值的是否有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,仍是接受但没有实现异常规范[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)的限定。因此,带完整限定信息的名字定义为:
::= []∞0 @
::= |
::= | |
| | |
其中,是组成名字标识符的ASCII码串,规定必须以@作为结尾后缀。是指构造函数、析构函数、运算符函数(operator function)、虚表(vtable)等内部数据结构等,详见特殊名字的编码。
与就是指C程序中的名字空间与类。是指实例化后的函数模板或类模板,详见模板实例化的名字编码。是对一个函数内部用花括号{ … }给出的不同的作用域(scope)的编号表示,详见编号名字空间。是对一个mangled name的ASCII码串中重复出现的类型或名字的简写表示方法,详见重复出现的名字与类型的简写表示。是对静态局部变量所在的函数名的表示方法,详见嵌套的名字。
数的编码
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[ ]∞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> ::= ?$ <函数模板的名字> <模板实参的编码> <函数的类型信息>

模板的名字以前缀?解析VC   Name Mangling 机制 - 图1 <函数模板的名字> <函数模板实参的编码>可以代替,?$ <类模板的名字> <类模板实参的编码>可以代替。
模板实参(template argument),可以分为类型名字(typename)与非类型(non-type)的常量两类。如果是类型名字或类作为模板实参,那么其编码格式详见类型的编码表示。如果模板实参是常量(constant),则已知的编码格式列为下表:
编码 含义
?x anonymous type template parameter x (‘template-parameter-x’)
$0a 整数值a
解析VC   Name Mangling 机制 - 图2Da anonymous type template parameter a (‘template-parametera’)
解析VC   Name Mangling 机制 - 图3%0A#card=math&code=Fab%092-tuple%20%7Ba%2Cb%7D%20%28unknown%29%0A)Gabc 3-tuple {a,b,c} (unknown)
解析VC   Name Mangling 机制 - 图4%0A#card=math&code=Hx%09%28unknown%29%0A)Ixy (unknown)
解析VC   Name Mangling 机制 - 图5%0A#card=math&code=Jxyz%09%28unknown%29%0A)Qa anonymous non-type template parameter a (‘non-type-template-parametera’)
上表中,用a, b, c表示有符号整数,而x, y, z表示无符号整数. 这些有符号整数或无符号整数的编码格式,详见数的编码。上表中,实数值的编码表示$2ab,a、b都是有符号整数,但计算实数的值时,实际上规范到以10为基数的科学计数法的表示形式。
例如:
template class one{
int i;
};

one one1; // mangled as ?one1@@3V?解析VC   Name Mangling 机制 - 图6one@H@,其中one@是模板的名字,
//H是模板实参int,其后的@是模板实参表结束标志

class Ce{};
one 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@@YANPAHHH0_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 tc{
public: void __stdcall func(tc){};
};

int main(int argc, char argv[])
{
tc ins;
ins.func(ins); /
tc::func(tc) mangled as ?func@?解析VC   Name Mangling 机制 - 图7tc@H@表示实例化的类模板tc,里面的H表示模板实例化的实参是整型
//Q表示public的成员函数;A表示非只读成员函数;E表示thiscall
//X表示函数返回类型void;
//V1@表示形参为一个class,class的名字为’’1’’号名字,本例中即tc的编码?$tc@H@;
//注意,本例中’’0’’号名字是函数名,即func@
//最后一个@表示函数的形参表结束;Z表示缺省的exception specification
return 0;
}
模板实例化时重复出现的实参的简写

模板实例化的名字编码,基本上就是用模板的名字与模板实参作为一个整体,当作或使用。因此模板实例化的名字在参与完成重复出现的类型简写与重复出现的名字简写两步处理之后,再单独处理模板实例化的模板实参表,对其内部重复出现的名字的编号与简化。其方法与重复出现的名字简写的处理相同。
例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@?解析VC   Name Mangling 机制 - 图8tc@VCe@@V1@@表示实例化的类模板tc,其中的V1@是类模板的实参的重复名字简写;
//函数func的作用域是tc,即tc::func编码为func@?$tc@VCe@@V1@@@ ;
//V1@表示成员函数func的第一个形参为一个类,类名为’’1’’号名字,本例中即tc,这是重复名字的简写;
//0表示该函数func的第二个形参与形参表中的’’0’’号形参相同,即tc,这是重复类型的简写

  1. 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 ??解析VC   Name Mangling 机制 - 图9func@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;
name9class1::name9(vv.ee); /
void name9class1::name9(class1::name9)
mangled as ??解析VC   Name Mangling 机制 - 图10%E7%9A%84%E5%90%AB%E4%B9%89%09%E5%89%8D%E7%BD%AE%E4%B8%8B%E5%88%92%E7%BA%BF()%E7%9A%84%E5%90%AB%E4%B9%89%0A%20%3F%09%E7%94%A8%E4%BA%8E%E8%A1%A8%E7%A4%BA%E6%A8%A1%E6%9D%BF%09%20%0A#card=math&code=name9%40V0class1%40%40%40%40YAXVname9%40class1%40%40%40Z%20%2A%2F%0A%2F%2F%20%20%20%20%20%20%20%20%20%20%20%20%E6%AD%A4%E4%BE%8B%E8%AF%B4%E6%98%8E%E6%A8%A1%E6%9D%BF%E5%AE%9E%E4%BE%8B%E5%8C%96%E5%9C%A8%E5%81%9A%E9%87%8D%E5%A4%8D%E5%90%8D%E5%AD%97%E7%AE%80%E5%8C%96%E6%97%B6%EF%BC%8C%0A%2F%2F%20%20%20%20%20%20%20%20%20%20%20%20%E6%A8%A1%E6%9D%BF%E5%AE%9E%E5%8F%82%E4%B8%AD%E7%9A%84name9%E4%B8%8E%E6%A8%A1%E6%9D%BF%E5%90%8D%E5%AD%97name9%E7%9B%B8%E5%90%8C%EF%BC%8C%E5%9B%A0%E8%80%8C%E7%AE%80%E5%8C%96%E4%B8%BA%E7%BC%96%E5%8F%B70%20%0A%20%20%20%20return%200%3B%0A%7D%0A%23%23%23%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E7%9A%84%E7%BC%96%E7%A0%81%E8%A1%A8%E7%A4%BA%0A%E8%BF%99%E9%87%8C%E6%89%80%E8%AF%B4%E7%9A%84%E7%B1%BB%E5%9E%8B%EF%BC%8C%E5%8C%85%E6%8B%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E3%80%81%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88%E7%9A%84%E7%B1%BB%E5%9E%8B%E3%80%81%E5%87%BD%E6%95%B0%E6%A8%A1%E6%9D%BF%E3%80%81%E7%B1%BB%E6%A8%A1%E6%9D%BF%E7%AD%89%E4%B8%8D%E9%9C%80%E8%A6%81%E5%88%86%E9%85%8D%E5%86%85%E5%AD%98%E7%A9%BA%E9%97%B4%E7%9A%84%E4%B8%80%E4%BA%9B%E6%A6%82%E5%BF%B5%E5%B1%9E%E6%80%A7%E3%80%82%E7%B1%BB%E5%9E%8B%E6%98%AF%E6%95%B0%E6%8D%AE%E5%AF%B9%E8%B1%A1%E4%B8%8E%E5%87%BD%E6%95%B0%E8%BF%99%E4%B8%A4%E7%B1%BB%E5%AE%9E%E4%BD%93%E7%9A%84%E5%B1%9E%E6%80%A7%E3%80%82%0A%E7%BC%96%E7%A0%81%09%E4%B8%8D%E5%B8%A6%E4%B8%8B%E5%88%92%E7%BA%BF%28%29%E7%9A%84%E5%90%AB%E4%B9%89%09%E5%89%8D%E7%BD%AE%E4%B8%8B%E5%88%92%E7%BA%BF%28_%29%E7%9A%84%E5%90%AB%E4%B9%89%0A%20%3F%09%E7%94%A8%E4%BA%8E%E8%A1%A8%E7%A4%BA%E6%A8%A1%E6%9D%BF%09%20%0A) 用于表示模板 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).
指针、引用、数组的类型编码
<指针类型的编码> ::=
<引用类型的编码> ::=
<一维数组类型的编码> ::= <指针类型的编码>
<多维数组类型的编码> ::=

其中作为前缀,用于区分各种情况的指针、引用、数组。指针自身是const还是volatile等访问属性,由确定。共有八种情况:
none const volatile const volatile
Pointer P Q R S
Reference A B
none ?, $$C
表示所指向的基类型(Referred type)是否具有const或volatile等访问属性,详见小节:。
一般地,取值为P,意为指针;取值为6,意为指针的基类型为near属性的非成员函数。
例如:
typedef const int (_stdcall _FP) (int i); / coded as P6G?BHH@Z */
// 其中?B表示为const
类成员指针的类型编码
指向类成员的指针,其编码为
=

其中各项的定义详见指针、引用、数组的类型编码。注意,是指基类型的属性,常用的值为: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的最末尾处对基类型访问属性的编码不同于普通的指针,要在最后加上所指向类的带完整限定信息的名字:
::= ?

有的文献称[7],类成员指针、类成员函数指针的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)
}
类成员函数指针的类型编码
类成员函数的指针(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 */
类成员函数的指针变量的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@’
复合类型(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@@
枚举类型(enum)的编码
<枚举类型的编码> ::= W <枚举实际使用的数据类型>
<枚举成员的编码> ::= W <枚举实际使用的数据类型> @

其中,W为枚举类型前缀词。为枚举类型的带限定的名字,是指按照名字所在的名字空间、所属的类,逐级列出限定情况(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

用于普通的数据对象,表示其是否具有const、volatile等访问属性;用于指针、数组、引用类型,则表明对基类型的访问属性,而指针自身是否为const、volatile等属性,则专由编码表示。
用于函数指针时,表示该指针所指向的基类型是函数。但与指向数据对象的普通指针不同——函数指针指向的基类型(即函数)也有自己的内存空间,只是这块内存空间必定是只读的、可执行的,因此函数指针所指向的基类型内存空间不存在const、volatile等访问属性。
的取值情况:
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
可以有0个或多个前缀:
Prefix Meaning
E type ptr64
F
unaligned type
I type restrict
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@@表示这是基于<::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。

给出了函数是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)”
使用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的用户看到的是修饰后的函数名字. 如果不希望使用复杂的C修饰后的函数名,替代办法是在DLL的.def文件中定义输出函数的别名,或者把函数声明为extern “C”.

在汇编源程序或者内联汇编中引用C/C函数

在汇编源程序或者内联汇编中引用了C/C函数,就必须引用该函数的修饰名字。

VC环境中C++ 语言中的 Name Mangling
在 C++ 语言中,函数需要支持重载,新增命名空间函数调用、类函数调用、运算符重载、模板函数等等,所以情况也比 C 语言复杂很多。
下面我们列举一些函数例子进行分析,函数可以有如下声明方式(其中 CALLTYPE 可以为 cdecl、stdcall、fastcall等):
1、void CALLTYPE fun();
2、int
CALLTYPE fun();
3、int CALLTYPE fun(int);
4、double
CALLTYPE fun(int, double);
5、int __CALLTYPE fun(int, char*);
6、class ABCD
{
public:
int __CALLTYPE fun();
};

7、template

int fun(typename T);
我们仍先假设此处 CALLTYPE 为 cdecl(即:#define CALLTYPE __cdecl),在 VC 中,Name Mangling 结果如下:

1、?fun@@YAXXZ
2、?fun@@YAHXZ
3、?fun@@YAHH@Z
4、?fun@@YANHN@Z
5、?fun@@YAPAHPAHPAD@Z
6、?fun@ABCD@@QAAHXZ
7、??$fun@H@@YAHH@Z

由此可见,C++ 的 Name Mangling 技术比 C 语言的复杂很多。
我们挑选第一条分析一下,“?”表示一个函数的开始,用以区别于 C 语言的“_”,fun 为函数名称,“@@YA”表示函数调用约定为 __cdecl,“X”表示函数的参数为空,“XZ”为结束标识。
将上述名称还原为可读方式并不复杂,但要记住这些规则,考虑到所有组合方式却是一件比较复杂的事情,下面我们来看看一个比较复杂的函数调用,声明函数如下:
int fun(const CString&, const std::vector&);
Name Mangling 后的结果为:
?fun@@YAHABV?解析VC   Name Mangling 机制 - 图11StrTraitATL@_WV?解析VC   Name Mangling 机制 - 图12vector@NV?$allocator@N@std@@@std@@@Z
如此长的一串,用人脑来直接分析显然不符合实际,好在 Windows 提供了 API 函数用于解析字符串,具体解析办法,下面一节将详细解释。

将Name Mangling 后的名称还原为可读的形式

在 Windows 的DbgHelp.dll 导出函数中,UnDecorateSymbolName 是用于解析 Name Mangling 字符串的,具体函数的细节可以查看 MSDN。如下为实例代码:
void UnDecorateName()
{
char szDecorateName[1024] = {0};
char szUnDecorateName[2048] = {0};
printf(“Please Input Decorated Name: “);
scanf(“%s”, szDecorateName);

if (UnDecorateSymbolName(szDecorateName, szUnDecorateName, sizeof(szUnDecorateName), UNDNAME_COMPLETE) == 0)
{
printf(“UnDecorateSymbolName Failed. GetLastError() = %d”, GetLastError());
getchar();
return;
}

printf("The UnDecorated Name Is: %s/r/n", szUnDecorateName);
getchar();
return;

}

在 Xp 中当我们输入如上的:?fun@@YAPAHPAHPAD@Z
程序得出的结果为:int __cdecl fun(int ,char *)
注意:在 Xp 中,带有模板的 Name Mangling 字符串无法直接还原,如需还原,可以在 Vista、Win7 中运行此程序。


  1. c++的一个重要的特性就是重载,一个类可以拥有多个同名函数。那么编译器是如何来区别重载函数的那?答案就是Name Mangling(或者叫做Name Decorate).
  2. Name Mangling就一个对函数进行哈希的算法,所以有的中文翻译为:函数签名。一个VC++函数经过函数签名后可能是这个结果:?Test2@@YAHXZ
  3. Name Mangling不是C特有的,只要是支持重载的高级语言就会有。对于VC来说,有一个WIN32 API可以解析这些经过编码后的函数签名。
  4. 这篇文章是研究如何Mangling的:VC++ Name Mangling
#include <Windows.h>
#include <Dbghelp.h>

#pragma comment(lib, "dbghelp.lib")

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,int nCmdShow)
{
    wchar_t * lpdecorate_name = L"?Test2@@YAHXZ";
    wchar_t readable[MAX_PATH]= {0};
    //
    if (::UnDecorateSymbolNameW(lpdecorate_name, readable,
        MAX_PATH, UNDNAME_COMPLETE))
    {
        ::MessageBox(NULL, readable, L"Readable Name", MB_OK);
    }
    return 0;
}