好的,各位观众老爷,今天咱们来聊聊 C++ 里一个挺有意思,但也经常让人头疼的玩意儿:Name Mangling 和 Demangling。简单来说,就是 C++ 编译器怎么给函数和变量起“暗号”,以及咱们怎么把这些“暗号”翻译回人话。
第一幕:啥是 Name Mangling?为啥要有这玩意?
首先,啥是 Name Mangling?中文里,有叫“名字修饰”的,也有叫“名字改编”的,意思都差不多。它指的是 C++ 编译器为了支持函数重载、命名空间、类等特性,把函数和变量的名字进行编码,变成一个更复杂、更独特的字符串。
为啥要这么干呢?你想想,C++ 允许函数重载,也就是可以有多个函数名字一样,但是参数列表不一样。比如:
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
如果我们直接用 add
这个名字,编译器咋知道你调用的是哪个 add
? 就算没有重载,C++还有命名空间, namespace A { void foo(); }
和 namespace B { void foo(); }
两个 foo
函数,名字一样,编译器也需要区分。
所以,编译器必须给每个函数和变量生成一个独一无二的名字,才能在链接的时候不出岔子。这个过程就是 Name Mangling。
第二幕:Name Mangling 的规则:一团乱麻?
Name Mangling 的规则比较复杂,而且不同的编译器(比如 GCC、Clang、MSVC)的规则还不一样!这真是让人头大。
不过,我们可以了解一些基本的规则,起码能看懂个大概。
-
基本原则: 编译器会把函数名、参数类型、命名空间、类名等信息编码到 Mangled Name 里。
-
常见符号:
_Z
(GCC/Clang): Mangled Name 的起始标志。?
(MSVC): Mangled Name 的起始标志。N
(GCC/Clang): 命名空间的开始。E
(GCC/Clang): 命名空间的结束。I
(GCC/Clang): 模板实例化的开始v
: voidi
: intd
: doublef
: floatP
: 指针
-
举个栗子 (GCC/Clang):
namespace MySpace { class MyClass { public: int myMethod(int x, double y); }; }
这个
myMethod
函数的 Mangled Name 可能是这样的:_ZN8MySpace7MyClass9myMethodEidi
分解一下:
_Z
: 起始标志N
: 命名空间开始8MySpace
: 命名空间名长度为 8,名字是 MySpace7MyClass
: 类名长度为 7,名字是 MyClass9myMethod
: 函数名长度为 9,名字是 myMethodE
: 命名空间结束i
: 第一个参数是 intd
: 第二个参数是 double
-
再来一个栗子 (MSVC):
int __stdcall MyFunction(int a, float b);
这个
MyFunction
函数的 Mangled Name 可能是这样的:?MyFunction@@YGHIH@Z
分解一下:
?
: 起始标志MyFunction
: 函数名@@
: 分隔符YG
: 调用约定 (__stdcall)H
: intI
: floatH
: int@Z
: 结束标志
表格总结 (GCC/Clang 常见符号):
符号 | 含义 |
---|---|
_Z |
Mangled Name 起始标志 |
N |
命名空间开始 |
E |
命名空间结束 |
C |
常量 (const) |
V |
volatile |
v |
void |
i |
int |
l |
long |
x |
long long |
f |
float |
d |
double |
b |
bool |
P |
指针 |
R |
引用 |
F |
函数类型 |
T |
模板 |
I |
模板实例化的开始 |
表格总结 (MSVC 常见符号):
符号 | 含义 |
---|---|
? |
Mangled Name 起始标志 |
@@ |
分隔符 |
Y |
调用约定 |
H |
int |
I |
float |
N |
double |
_ |
__stdcall 调用约定 |
A |
__cdecl 调用约定 |
E |
__thiscall 调用约定 |
M |
静态成员 |
Q |
const |
R |
引用 |
@Z |
结束标志 |
重要提示: 这些规则只是个大概,具体的 Mangling 规则非常复杂,而且会随着编译器版本变化。不要试图完全记住所有规则,理解基本原理就好。
第三幕:Demangling:把“暗号”翻译成人话
Name Mangling 后的名字,人类是看不懂的。所以,我们需要 Demangling,也就是把 Mangled Name 翻译回人话。
-
工具: 很多编译器都提供了 Demangling 工具。
c++filt
(GCC/Clang): Unix-like 系统下常用的 Demangling 工具。undname
(MSVC): Windows 下的 Demangling 工具。
-
使用方法:
# GCC/Clang c++filt _ZN8MySpace7MyClass9myMethodEidi # MSVC (在 Visual Studio 开发人员命令提示符中) undname "?MyFunction@@YGHIH@Z"
-
在线 Demangler: 网上也有很多在线 Demangler 工具,方便快捷。
-
代码 Demangling: 也可以在代码里进行 Demangling,但这通常需要使用特定的库或者编译器扩展,可移植性较差,不太推荐。
第四幕:实战演练:查看 Mangled Name
怎么知道一个函数或者变量的 Mangled Name 呢?
- 编译器的输出: 编译器的错误信息和警告信息里,有时会包含 Mangled Name。
- 反汇编工具: 使用反汇编工具(比如
objdump
、IDA Pro
)可以查看编译后的目标文件里的 Mangled Name。 - 链接器: 链接器在链接过程中也会用到 Mangled Name,可以在链接器的输出里找到。
- nm 工具:
nm
工具可以列出一个目标文件或者库文件中的符号表,其中就包含 Mangled Name。
举个栗子:
-
创建 C++ 文件 (test.cpp):
#include <iostream> namespace MySpace { class MyClass { public: int myMethod(int x, double y) { std::cout << "Hello from myMethod!" << std::endl; return 0; } }; } int main() { MySpace::MyClass obj; obj.myMethod(10, 3.14); return 0; }
-
编译:
g++ -c test.cpp -o test.o
-
使用 nm 查看 Mangled Name:
nm test.o
输出结果可能包含类似这样的行:
0000000000000000 T _ZN8MySpace7MyClass9myMethodEidi
这就是
myMethod
函数的 Mangled Name。 -
使用 c++filt 进行 Demangling:
c++filt _ZN8MySpace7MyClass9myMethodEidi
输出结果:
MySpace::MyClass::myMethod(int, double)
是不是清晰多了?
第五幕:Name Mangling 的应用场景
- 动态链接库 (DLL/SO): 动态链接库需要使用 Mangled Name 来解决符号冲突和函数重载的问题。
- 库的兼容性: 由于不同编译器的 Mangling 规则不一样,所以用不同编译器编译的库,可能无法直接链接在一起。
- 调试: 在调试过程中,如果看到 Mangled Name,可以使用 Demangling 工具来理解代码的含义。
- 逆向工程: 在逆向工程中,理解 Name Mangling 规则可以帮助分析代码的结构。
第六幕:Name Mangling 的坑:兼容性问题
Name Mangling 最大的问题就是兼容性。由于不同编译器 Mangling 规则不一样,所以:
- 不同编译器编译的库,不能混用。 比如,用 GCC 编译的库,不能直接链接到用 MSVC 编译的程序里。
- 即使是同一个编译器,不同版本之间,Mangling 规则也可能发生变化。 这会导致库的二进制兼容性问题。
为了解决这个问题,可以考虑以下方案:
- 使用 C 接口: C 语言没有 Name Mangling,所以可以使用 C 接口作为桥梁,连接不同编译器编译的代码。
- 使用标准 ABI: 一些平台定义了标准的 ABI (Application Binary Interface),规定了 Name Mangling 的规则,可以提高兼容性。
- 避免使用复杂的 C++ 特性: 尽量避免使用复杂的 C++ 特性(比如模板、重载等),可以减少 Name Mangling 带来的问题。
第七幕:总结与建议
Name Mangling 是 C++ 编译器为了支持函数重载、命名空间等特性而采用的一种技术。虽然 Mangling 规则比较复杂,而且容易引起兼容性问题,但是它是 C++ 语言的重要组成部分。
- 理解 Name Mangling 的基本原理,可以帮助我们更好地理解 C++ 的底层机制。
- 学会使用 Demangling 工具,可以方便地查看和理解 Mangled Name。
- 在实际开发中,要尽量避免 Name Mangling 带来的兼容性问题。
给各位观众老爷的建议:
- 不要试图完全记住所有 Mangling 规则。
- 善用 Demangling 工具。
- 关注编译器的文档,了解最新的 Mangling 规则。
- 在需要兼容性的场景下,尽量使用 C 接口。
好了,今天的 Name Mangling 和 Demangling 就聊到这里。希望各位观众老爷有所收获! 咱们下次再见!