第6.7章: 翻译单元
概述
C++ 程序常被拆分到多个文件中。每个文件被单独编译成一个翻译单元, 然后由链接器把所有翻译单元合并成最终的可执行文件。
现代 C++ 还引入了模块,它是被编译的翻译单元, 可以取代许多头文件的使用场景。
在本章中,你将学到:
- 什么是翻译单元
- 声明与定义如何组织
- 分离编译如何工作
- C++ 模块与翻译单元的关系
- 如何编写并编译一个简单的模块
1. 什么是翻译单元?
翻译单元是对单个 .cpp 文件应用预处理器
(#include、#define 等)后得到的结果,
也就是编译器真正去编译的那一个大文件。
main.cpp → translation unit A
math.cpp → translation unit B
io.cpp → translation unit C
每个翻译单元被独立编译成一个目标文件:
main.cpp → main.o
math.cpp → math.o
io.cpp → io.o
然后链接器把所有目标文件合并成一个可执行文件。
2. 声明 vs. 定义
处理多个翻译单元时,把声明与定义分开 至关重要。
- 声明告诉编译器某个东西存在。
- 定义提供完整函数体,或分配存储空间。
声明放在头文件(.hpp)中。
定义放在源文件(.cpp)中。
// math.hpp (declaration)
int square(int x);
// math.cpp (definition)
int square(int x)
{
return x * x;
}
// main.cpp
#include "math.hpp"
int main()
{
::fast_io::io::println(square(5));
}
3. 编译多个文件
你可以在一条命令中编译并链接多个翻译单元:
clang++ -std=c++23 main.cpp math.cpp -o program
或者用 g++:
g++ -std=c++23 main.cpp math.cpp -o program
也可以分别编译每个翻译单元,之后再链接:
clang++ -std=c++23 -c main.cpp # produces main.o
clang++ -std=c++23 -c math.cpp # produces math.o
clang++ main.o math.o -o program
4. 模块作为翻译单元
C++ 模块接口文件(.cppm)同样是一个翻译单元。
模块不使用 #include 复制文本,而是用
export 与 import 共享已编译的
声明。
math.cppm → translation unit (module interface)
main.cpp → translation unit (imports the module)
编译器从模块接口生成 PCM(预编译模块), 其他翻译单元再导入它。
5. 编写并编译一个简单模块
模块接口声明一个模块,并导出你希望其他翻译单元 使用的函数或类型。
// math.cppm — module interface
export module math;
export int square(int x)
{
return x * x;
}
另一个翻译单元可以导入该模块:
// main.cpp
import math;
import fast_io;
int main()
{
::fast_io::io::println(square(7));
}
用 Clang 编译模块
第 1 步 —— 编译模块接口
clang++ -std=c++26 --precompile math.cppm -o math.pcm
第 2 步 —— 编译你的程序并导入 PCM
clang++ -std=c++26 -fmodule-file=math=math.pcm -c main.cpp -o main.o
第 3 步 —— 链接最终可执行文件
clang++ main.o -o program
可选:编译 fast_io 模块
clang++ -std=c++26 --precompile \
$HOME/libraries/fast_io/share/fast_io/fast_io.cppm \
-I$HOME/libraries/fast_io/include \
-o fast_io.pcm
clang++ -std=c++26 \
-fmodule-file=math=math.pcm \
-fmodule-file=fast_io=fast_io.pcm \
-c main.cpp -o main.o
clang++ main.o -o program
要点
- 每个
.cpp文件都变成一个独立的翻译单元。 - 模块接口文件(
.cppm)也是翻译单元。 - 编译器独立编译每个翻译单元。
- 链接器把目标文件合并成最终可执行文件。
- 模块用已编译的接口取代文本式包含。
- Clang 用 PCM(
.pcm)表示已编译的模块。