C++20模块能否消除翻译单元边界优化限制及可见边界解析
问题描述
如果项目完全不使用#include,仅采用C++20模块,编译器能否看到所有函数体?
举个例子(出自CppCon 2022中Ofek Shilon的演讲《LLVM Optimization Remarks》21:28处):
void somefunc(const int&); int whateva(); void f(int i, int* res) { somefunc(i); i++; res[0] = whateva(); i++; res[1] = whateva(); i++; res[2] = whateva(); }
在传统翻译单元(TU)中,编译器无法获取somefunc和whateva的函数体,会错失大量优化机会。
如果改用纯C++20模块方案,这个问题是否能解决?已知模块会生成类似预编译头的模块元数据文件,其他模块导入时需通过命令行引入这些文件。
这是否意味着所谓的“翻译单元边界劣化”(用户自创名称)会彻底消失,所有模块都能通过预编译模块看到所有函数体?
换句话说,当项目仅用模块(可能依赖动态链接库)、忽略LTO的情况下,新的“编译器可见边界”是什么?
回答
核心结论
纯C++20模块方案不会彻底消除函数体可见性的边界限制,编译器的可见范围依然受模块的导出规则和编译阶段约束。
详细说明
模块导出规则决定可见性
模块中只有被显式标记为export的实体才能被其他模块访问。如果somefunc和whateva所在模块仅导出函数声明而非完整定义(函数体),导入该模块的翻译单元依然只能看到函数签名,无法获取实现细节,优化空间仍会受限。只有当模块导出函数的完整定义时,导入方才能拿到函数体执行内联、常量传播等优化——但模块作者完全可以选择只导出声明,隐藏实现。预编译模块不改变可见性逻辑
预编译模块文件是编译器对模块内容的预处理缓存,作用是加快编译速度,而非突破可见性规则。它只是提前处理好模块允许导出的内容,导入时直接复用,不会暴露模块内部未导出的实体。新的编译器可见边界
忽略LTO的前提下,编译器的可见边界是当前翻译单元 + 所导入模块中已导出的完整实体定义:- 若函数定义在当前TU内,或在导入模块中被导出了完整实现,编译器能看到函数体;
- 若函数仅在模块中导出声明,或是动态链接库的外部符号,编译器依然看不到函数体,无法进行跨边界优化。
“翻译单元边界劣化”并未彻底消失
模块确实解决了传统头文件重复包含导致的TU膨胀问题,但只是把基于头文件的TU边界,替换成了基于模块导出规则的边界。如果模块设计时刻意隐藏函数实现,编译器依然无法跨边界获取函数体,优化受限的情况依然存在。
内容的提问来源于stack exchange,提问作者ABu

