C++ 的 命名重整(Name Mangling) 是编译器将 C++ 中的函数、变量、类等标识符转换为唯一且可链接的符号名的过程。这是由于 C++ 支持函数重载、命名空间、模板、类成员等特性,而底层链接器(如 ELF 链接器)只支持简单的全局符号名。
C++ 一直为人诟病之一的原因是他的二进制模块兼容性不好,即ABI(Application Binary Interface)问题。对于同一源代码,不同编译器,甚至同一编译器不同版本都不兼容,其编译出来的ABI不能相互使用。比如其中一个ABI问题是为了支持函数重载,C++使用了Name Mangling(翻译为命名重整、名字改编、名字修饰等)技术,而Name Mangling在不同编译器间基本是完全不兼容的。
Name Mangling是一种在编译过程中,将函数、变量的名称重新改编的机制,简单来说就是编译器为了区分各个函数,将函数通过一定算法,重新修饰为一个全局唯一的名称。
C++除了支持函数重载,也即是允许多个函数拥有一样的名字,同时也支持命名空间,也即同时允许多个同样的函数定义在在不同的名称空间。这使得Name mangling尤其复杂。

🔍 为什么需要命名重整?
C 语言中:
cpp
int add(int a, int b);
float add(float a, float b); // ❌ 编译错误!C 不支持重载
C++ 中合法:
cpp
int add(int a, int b);
float add(float a, float b); // ✅ 合法!但链接器看到两个 "add" 会冲突
→ 编译器必须把它们变成不同的符号,比如:
_Z3addii_Z3addff
这样链接器就能区分了。
🧩 命名重整规则(Itanium ABI)
主流编译器(GCC、Clang、Android NDK、鸿蒙 NDK)都遵循 Itanium C++ ABI 标准。
基本格式:
_Z [嵌套名称] [参数编码]
常见编码示例:
| C++ 原始代码 | Mangled Name | 说明 |
|---|---|---|
void foo() |
_Z3foov |
3foo = 名字长度+名字,v = void |
int add(int, int) |
_Z3addii |
i = int |
std::string f() |
_Z1fB5cxx11Ss 或 _Z1fv(旧 ABI) |
Ss = std::string(新 ABI 用 B5cxx11 表示 inline namespace) |
namespace ns { void bar(); } |
_ZN2ns3barEv |
N...E 表示嵌套,2ns = "ns" 长度 2 |
class A { void func(); }; |
_ZN1A4funcEv |
类成员函数 |
template<typename T> void g(T); g<int>(42); |
_Z1gIiEvT_ |
模板实例化 |
🔧 常用类型编码(部分)
| 类型 | 编码 |
|---|---|
void |
v |
bool |
b |
char |
c |
int |
i |
unsigned int |
j |
long |
l |
float |
f |
double |
d |
const T& |
RKT_ |
T* |
PT_ |
std::string |
Ss(实际是 std::basic_string<char> 的别名) |
std::string_view |
NSt7__n117basic_string_viewIcNS_11char_traitsIcEEEE(简化后常为 St17basic_string_viewIcSt11char_traitsIcEE) |
💡 注意:
std::常缩写为St,std::__cxx11可能编码为St7__n1或类似。
✅ 如何解码(Demangle)?
方法 1:使用 c++filt(最常用)
echo "_ZN3pie13ICacheService2ofERKNSt17basic_string_viewIcSt11char_traitsIcEEEE" | c++filt
输出:
cpp
pie::ICacheService::of(std::basic_string_view<char, std::char_traits<char> > const&)
方法 2:在代码中使用 abi::__cxa_demangle(C++ 运行时)
cpp
#include <cxxabi.h>
#include <memory>
#include <string>
std::string demangle(const char* name) {
int status = -1;
std::unique_ptr<char, void(*)(void*)> res {
abi::__cxa_demangle(name, nullptr, nullptr, &status),
std::free
};
return (status == 0) ? res.get() : name;
}
方法 3:工具链自带
-
nm -C:自动解码符号(GNU nm)bashnm -C libaccount.so | grep ICacheService -
objdump -C:反汇编时解码 -
Android Studio / DevEco:调试时自动显示原始函数名
⚠️ 常见问题与注意事项
1. 不同编译器 mangle 规则不同
- MSVC(Windows)使用自己的规则(如
?func@@YAXH@Z),不兼容 Itanium ABI - GCC/Clang/NDK 使用 Itanium ABI → 兼容
2. ABI 兼容性问题
- GCC 5+ 引入
_GLIBCXX_USE_CXX11_ABI=1,导致std::string符号变为std::__cxx11::string - 如果一个库用新 ABI,另一个用旧 ABI,即使函数存在也会"symbol not found"
3. 静态成员 vs 普通成员
- 静态成员函数:mangled 名不含
this,类似普通函数 - 普通成员函数:隐含
this指针,但不影响符号名(调用约定处理)
4. 内联函数 / 模板可能无符号
- 如果函数被内联或未实例化,可能不会出现在
.so的符号表中
🛠️ 实用命令汇总
| 任务 | 命令 |
|---|---|
| 解码符号 | c++filt _Z3foov |
| 查看 .so 符号(已解码) | nm -C libxxx.so |
| 查看未定义符号 | `nm -u libxxx.so |
| 搜索特定函数 | `nm -D libxxx.so |
| 检查是否 strip | file libxxx.so → 看是否含 "stripped" |
✅ 总结
| 概念 | 说明 |
|---|---|
| 命名重整 | C++ 编译器将函数名转为唯一符号的过程 |
| 目的 | 支持重载、命名空间、模板等特性 |
| 标准 | Linux/Android/HarmonyOS 使用 Itanium ABI |
| 解码工具 | c++filt, nm -C, abi::__cxa_demangle |
| 典型错误 | "symbol not found" 往往是 mangled 名不匹配(ABI/版本/缺失) |