哈喽,各位好!
今天咱们来聊聊C++里一个有点“神秘”,但又无处不在的东西:Name Mangling(名字修饰)与 Demangling(名字反修饰)。这玩意儿就像C++编译器给函数、变量起的小名,目的是让它们在二进制文件里区分开来,避免重名冲突。听起来是不是有点像给幼儿园小朋友编号?
准备好了吗?咱们开始吧!
1. 为什么要Name Mangling?
想象一下,如果没有Name Mangling,会发生什么?
假设你有两个文件:
-
file1.cpp:
int add(int a, int b) { return a + b; }
-
file2.cpp:
double add(double a, double b) { return a + b; }
这两个文件里都有一个名为 add
的函数,但它们的参数类型不同。如果没有Name Mangling,编译器编译后会得到两个同名的函数,链接器在链接的时候就会懵逼:“我该用哪个add
呢?” 这就导致了链接错误。
Name Mangling 就是为了解决这个问题。它通过在函数名后面加上一些信息,比如参数类型、类名、命名空间等,来生成一个独一无二的名字,让链接器可以区分不同的函数。
2. Name Mangling 的规则
C++标准没有规定Name Mangling的具体规则,因此不同的编译器(比如GCC、Clang、MSVC)有不同的实现方式。但它们的基本思路是相似的:把函数签名(包括函数名、参数类型、返回值类型、所属类、命名空间等)编码到函数名中。
咱们以GCC为例,来简单了解一下Name Mangling的规则:
-
全局函数: 通常以
_Z
开头,后面跟着函数名的长度和函数名本身,然后是参数类型的编码。例如:
int add(int a, int b)
会被 Mangling 成_Z3addii
(3表示函数名add
的长度,ii
表示两个int类型的参数)。 -
成员函数: 会包含类名和作用域信息。
例如:
class MyClass { public: int method(int a); };
会被 Mangling 成_ZN7MyClass6methodEi
(7表示类名MyClass
的长度,6表示函数名method
的长度,E
表示结束,i
表示int
类型的参数)。 -
命名空间: 会包含命名空间的信息。
例如:
namespace MyNamespace { int func(); }
会被 Mangling 成_ZN11MyNamespace4funcEv
(11表示命名空间名MyNamespace
的长度,4表示函数名func
的长度,v
表示void
,没有参数)。
当然,这只是一个非常简化的描述。实际的Name Mangling规则要复杂得多,涉及到各种类型、模板、const修饰符、异常规范等等。
3. Name Mangling 的例子
咱们来看一些具体的例子,感受一下Name Mangling的威力:
函数签名 | GCC Mangled Name | Clang Mangled Name | MSVC Mangled Name | 说明 |
---|---|---|---|---|
int add(int a, int b); |
_Z3addii |
_Z3addii |
?add@@YAHHH@Z |
简单的全局函数,两个int参数 |
double add(double a, double b); |
_Z3adddd |
_Z3adddd |
?add@@YANNN@Z |
简单的全局函数,两个double参数 |
void print(); |
_Z5printv |
_Z5printv |
?print@@YAXXZ |
简单的全局函数,无参数 |
namespace MySpace { int foo(); } |
_ZN7MySpace3fooEv |
_ZN7MySpace3fooEv |
?foo@MySpace@@YAHXZ |
包含命名空间的函数 |
class MyClass { public: int bar(); }; |
_ZN7MyClass3barEv |
_ZN7MyClass3barEv |
?bar@MyClass@@QAEHXZ |
类成员函数 |
int add(int a, int b) noexcept; |
_Z3addiiNSt9nothrowtE |
_Z3addiiNSt9nothrowtE |
?add@@YAHHH@Z (MSVC不encode noexcept) |
noexcept函数,注意GCC和Clang会编码noexcept关键字 |
template <typename T> T max(T a, T b); |
_Z3maxIiET_S0_S0_ |
_Z3maxIiET_S0_S0_ |
??$max@H@@YAHHH@Z |
函数模板,Ii 表示 int 类型 |
int MyClass::operator+(int other) { return 0; } |
_ZN7MyClassplEi |
_ZN7MyClassplEi |
??HMyClass@@QAEHH@Z |
重载运算符,注意pl 代表operator+ |
int add(int a, int b, int c = 0); |
_Z3addiii |
_Z3addiii |
?add@@YAHHHH@Z |
带有默认参数的函数。 注意:GCC和Clang会编码所有的参数类型,即使有默认参数。 MSVC也编码所有的参数类型。 |
注意: 以上只是一些简单的例子,实际的Name Mangling规则要复杂得多。不同的编译器和不同的C++标准可能会产生不同的Mangled Name。
咱们可以用一些工具来查看Name Mangling的结果,比如:
-
c++filt
(Linux/macOS): 这是一个命令行工具,可以用来 Demangle Mangled Name。例如:
c++filt _Z3addii
会输出add(int, int)
。 -
undname
(Windows): 这是MSVC提供的工具,可以用来 Demangle MSVC Mangled Name。例如:
undname "?add@@YAHHH@Z"
会输出int __cdecl add(int,int)
4. Demangling 的作用
Demangling 就是 Name Mangling 的逆过程,它可以把 Mangled Name 转换回原始的函数签名,让人更容易理解。
Demangling 在以下情况下非常有用:
-
调试: 当你在调试器里看到 Mangled Name 时,可以用 Demangling 工具将其转换回原始的函数签名,方便你理解代码的逻辑。
-
分析崩溃报告: 崩溃报告里通常会包含 Mangled Name,可以用 Demangling 工具将其转换回原始的函数签名,帮助你定位问题。
-
逆向工程: 在逆向工程中,Demangling 可以帮助你理解二进制代码的功能。
5. 如何在代码中使用 Demangling
虽然我们可以使用 c++filt
或 undname
这样的工具来 Demangle Mangled Name,但有时候我们也需要在代码中进行 Demangling。
C++标准库并没有提供 Demangling 的函数,但我们可以使用编译器提供的扩展来实现。
-
GCC/Clang: 可以使用
abi::__cxa_demangle
函数。#include <iostream> #include <cxxabi.h> #include <string> std::string demangle(const char* mangled_name) { int status = -4; // some arbitrary value to detect errors std::unique_ptr<char, void(*)(void*)> res { abi::__cxa_demangle(mangled_name, NULL, NULL, &status), std::free }; return (status==0) ? res.get() : mangled_name; } int main() { std::cout << demangle("_Z3addii") << std::endl; // Output: add(int, int) return 0; }
注意:
abi::__cxa_demangle
是 GCC/Clang 的扩展,不是C++标准的一部分。 -
MSVC: 可以使用
UnDecorateSymbolName
函数。#include <iostream> #include <string> #include <windows.h> #include <memory> std::string demangle(const char* mangled_name) { std::unique_ptr<char[]> buffer(new char[1024]); // 假设buffer足够大 DWORD result = UnDecorateSymbolName( mangled_name, buffer.get(), 1024, UNDNAME_COMPLETE // Use full expansion ); if (result != 0) { return std::string(buffer.get()); } else { return mangled_name; // Demangling failed, return original name } } int main() { std::cout << demangle("?add@@YAHHH@Z") << std::endl; return 0; }
注意:
UnDecorateSymbolName
是 Windows API 的一部分,不是C++标准的一部分。
6. Name Mangling 的影响
Name Mangling 虽然解决了函数重名的问题,但也带来了一些影响:
-
可读性降低: Mangled Name 很难阅读和理解,给调试和分析代码带来了一定的困难。
-
ABI兼容性问题: 不同的编译器使用不同的Name Mangling规则,导致不同编译器编译的代码在链接时可能会出现问题。这就是ABI(Application Binary Interface)兼容性问题。
-
动态链接库版本问题: 如果动态链接库的函数签名发生变化(比如参数类型改变),会导致Name Mangling的结果发生变化,从而导致链接错误。
为了解决ABI兼容性问题,可以采取以下措施:
-
使用C接口: C语言没有Name Mangling,因此可以使用C接口来避免ABI兼容性问题。
-
使用稳定的ABI: 有些编译器提供了稳定的ABI选项,可以保证Name Mangling的结果在不同的编译器版本之间保持一致。
-
使用版本控制: 对动态链接库进行版本控制,可以避免因函数签名变化导致的链接错误。
7. extern "C" 的作用
extern "C"
是一个C++关键字,用于告诉编译器按照C语言的规则来编译代码。这意味着:
-
不进行Name Mangling: 使用
extern "C"
声明的函数不会进行Name Mangling,而是使用原始的函数名。 -
使用C调用约定: 使用
extern "C"
声明的函数使用C语言的调用约定,而不是C++的调用约定。
extern "C"
通常用于以下情况:
-
与C代码交互: 当C++代码需要调用C代码时,需要使用
extern "C"
来声明C函数,以避免Name Mangling和调用约定不匹配的问题。 -
创建C接口: 当需要创建一个C接口供其他语言调用时,需要使用
extern "C"
来声明C函数,以避免Name Mangling。
// C++ header file (myheader.h)
#ifndef MYHEADER_H
#define MYHEADER_H
#ifdef __cplusplus
extern "C" {
#endif
int my_c_function(int x); // A C-style function
#ifdef __cplusplus
}
#endif
#endif // MYHEADER_H
// C++ implementation file (mycppfile.cpp)
#include "myheader.h"
int my_c_function(int x) {
return x * 2;
}
8.总结
Name Mangling 是C++编译器为了解决函数重名问题而采用的一种技术,它通过在函数名后面加上一些信息来生成一个独一无二的名字。Demangling 是 Name Mangling 的逆过程,它可以把 Mangled Name 转换回原始的函数签名。 Name Mangling 带来了一些影响,比如可读性降低和ABI兼容性问题。可以使用 extern "C"
来避免Name Mangling。
表格总结:
特性 | Name Mangling | Demangling | extern "C" |
---|---|---|---|
目的 | 解决函数重名问题,生成唯一符号 | 将Mangled Name还原为原始函数签名 | 避免Name Mangling,使用C调用约定 |
适用场景 | C++编译,需要区分不同函数签名的情况 | 调试、分析崩溃报告、逆向工程 | 与C代码交互、创建C接口 |
影响 | 可读性降低、ABI兼容性问题 | 无 | 限制C++特性,降低类型安全性 |
编译器支持 | 各编译器实现不同规则 | c++filt (GCC/Clang), undname (MSVC), 编译器扩展 |
C++关键字,所有编译器都支持 |
代码示例 | (见前面的例子) | (见前面的例子) | (见前面的例子) |
解决的问题 | 函数重载、命名空间、类成员函数等带来的重名问题 | 方便人类阅读和理解Mangled Name | 与C代码的链接和调用问题,生成C风格的API |
带来的问题 | ABI兼容性,链接时可能出现问题 | 需要额外的工具或代码来实现 | 降低C++代码的类型安全性和灵活性,无法使用C++的一些高级特性 |
好啦,今天关于C++ Name Mangling 和 Demangling 的分享就到这里。希望大家有所收获!下次再见!