C++中的名称修饰与命名空间

名称修饰/名字修饰(Name Mangling)

名称修饰(Name Mangling)是一个很多编程人员容易忽视的cpp特性,现代C++编译器基本上都具有名称修饰的特性,名称修饰的主要用途是将函数、结构体、变量的名称进行加密(Encode),以便将更加具有语义的信息传递给Linker,防止由于声明了相同名称但类型不同/命名空间不同的变量、函数、结构体而引发的冲突。考虑以下三个C++函数:

1
2
3
4
5
6
7
8
9
10
//File name: shared.h
//header file. without function implementation.
#pragma once

namespace external{
void helloworld();
int helloworld(int args);
}

void helloworld();

现代C++允许函数重名,即使在同一个命名空间或全局命名空间内,只要拥有不同的入参即可。当编译器编译代码为二进制码时,其需要使用一些符号来表示每个函数名所指向的地址,那么这些符号名(函数名)必须是可区分的。因此C++会讲这些符号名进行修饰,最后使得它们最终能够以不同的符号存在。我们可以通过nm指令来查看我们编译后的二进制程序内的符号表。

完成上述示例头文件的函数实例,随后进行编译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// File name: shared.cpp
// Implement the function define in header file.

#include <iostream>
#include "shared.h"

void external::helloworld() {
std::cout << "Hello, World(from external)!" << std::endl;
}

int external::helloworld(int a) {
std::cout << "Hello, World with int: " << a << std::endl;
return a;
}

void helloworld() {
std::cout << "Hello, World(global)!" << std::endl;
}

为了后续将其导出为动态链接库,使用PIC编译,编译指令如下:

1
2
# Using -fPIC flag to complied with position indpendent code.
$ g++ -c -fPIC ./shared.cpp -o ./shared.o

使用nm指令查看符号表,指令与输出如下:

1
2
3
4
5
6
7
8
9
10
11
$ nm ./shared.o

U _GLOBAL_OFFSET_TABLE_
000000000000007d T _Z10helloworldv
0000000000000032 T _ZN8external10helloworldEi
0000000000000000 T _ZN8external10helloworldEv
U _ZNSolsEPFRSoS_E
U _ZNSolsEi
U _ZSt21ios_base_library_initv
U _ZSt4cout
...

可见,*g++*编译器会使用_Zsymbol这类格式,三个在源码内同名的函数最终被编译器编译成为了不同的符号,其中带有external命名空间的函数的符号名也会带有external字样。

名称修饰与extern “C”

当我们需要调用外部二进制程序(使用诸如shared library等方式)时,当然需要调用特定内存地址所代表的符号名。如果我们将之前编译好的可执行文件导出为动态链接库(.so文件),并在其他cpp程序内进行调用,最终在编译时编译器一定需要以修饰后的名称进行函数调用。这也就意味着,此时的编译器需要了解链接的动态库的名称修饰方式。这也便是C++ ABI(Application Binary Interface)的一部分,不同的编译器可能遵循有不同的ABI,因此得到不同的名称修饰结果(或者数据编排格式(Data layout)、函数调用约定(Calling Convention)、STL实现)等,这时候直接进行链接就会失败。更多关于ABI的描述可以查看这篇stackoverflow的讨论ABI的英文维基页面

简单来说,我们需要一个统一的符号约定方式,以便我们统一ABI并实现在不同程序的调用。这种方式就是常见的extern "C"。可以看到在很多开源库的函数声明时都会带有extern "C"修饰符,extern "C"的用途很简单:使用C语言的名称修饰方式————不修饰。

还是以上述的代码为例,假如我们使用extern "C"对函数声明进行修饰,那么我们便无法声明同名函数,命名空间的修饰也会一并失效

1
2
3
4
5
6
7
8
9
10
11
12
13

//File name: shared.h
//header file. without function implementation.
#pragma once
extern "C"{
namespace external {
void helloworld();
int helloworld(int a);
}
void helloworld();

}

编译上述代码我们会得到错误:

1
2
3
4
5
6
$ g++ -C -fPIC ./shared.cpp -o ./shared.o

In file included from ./shared.cpp:2:
./shared.h:6:13: error: conflicting declaration of C function ‘int external::helloworld(int)’
6 | int helloworld(int a);

可见,使用C语言修饰时,我们无法重载函数,也不能在不同的命名空间命名同名函数。其实,如果我们编译仅有一个函数且附带命名空间的代码即:

1
2
3
4
5
6
7
8
9
10

//File name: shared.h
//header file. without function implementation.
#pragma once
extern "C"{
namespace external {
void helloworld();
}
}

使用nm指令查看其符号表:

1
2
3
4
5
6
$ nm ./shared.o

...
U _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
0000000000000000 T helloworld

我们可以发现这里的函数符号根本没有附带任何的命名空间名称,也就是说在extern "C"内使用命名空间是无效的。


C++中的名称修饰与命名空间
https://blog.bakeneko-kuro.com/2025/12/29/cpp/cpp-name-mangling/
作者
迷途黑猫
发布于
2025年12月29日
许可协议