Clang与GCC对非constexpr变量调用constexpr函数的编译期求值差异及解决方法
问题分析与解决办法
这是个很典型的编译器优化策略差异问题,本质上是C++标准给编译器留下的优化空间导致的不同实现,我来详细解释下:
为什么会出现这个差异?
首先得明确C++标准里对constexpr函数的规定:
constexpr函数是潜在的常量表达式,它既可以在编译期被求值,也可以在运行期被调用。- 只有当上下文强制要求编译期常量时(比如初始化
constexpr变量、作为模板参数、指定数组大小等),编译器才必须在编译期计算函数结果。 - 对于普通
int变量的初始化,标准并没有强制要求编译期求值——这属于编译器的自主优化范围,要不要做常量折叠完全看编译器的策略。
回到你的例子:
- GCC 7.1在这里采取了更积极的常量折叠优化:它识别到
sum(4,5)的参数都是常量,函数逻辑简单,即使初始化的是普通int变量,也直接在编译期算出结果9,避免运行时调用。 - Clang 9则更严格遵循"非强制常量上下文不主动做编译期求值"的原则:只有当变量被声明为
constexpr(此时上下文要求常量),才会触发编译期计算;普通int变量的初始化,它选择保留运行时调用的逻辑(当然开启高优化级别后可能会改变)。
两种行为都是完全符合C++标准的,不存在谁对谁错,只是编译器团队的优化策略不同而已。
如何解决这个问题?
根据你的需求不同,有几种方案可选:
1. 既要编译期求值,又允许运行时修改变量
如果你希望变量的初始值在编译期确定,但后续可以修改,有两种方式:
- C++20及以上版本:使用
constinit关键字,它要求初始化器是常量表达式,保证编译期初始化,但变量本身是可修改的:constexpr int sum(int a, int b) { return a + b; } int main() { constinit int a = sum(4, 5); // 编译期求值,a可修改 a = 10; // 合法 return 0; } - C++20之前版本:先用
const变量接收编译期结果,再拷贝到普通变量:constexpr int sum(int a, int b) { return a + b; } int main() { const auto temp = sum(4, 5); // 编译期求值 int a = temp; // 运行期拷贝,a可修改 a = 10; // 合法 return 0; }
2. 强制编译期求值,不允许修改变量
这就是你提到的方案,直接用constexpr声明变量,所有编译器都会在编译期求值:
constexpr int sum(int a, int b) { return a + b; } int main() { constexpr int a = sum(4, 5); // 编译期求值,a不可修改 return 0; }
3. 通过编译器优化选项让Clang主动做常量折叠
如果你不想修改代码,只是希望Clang在普通int变量场景下也做编译期求值,可以开启更高的优化级别,比如-O2或-O3:
clang-9 -O2 your_code.cpp
开启优化后,Clang会识别到sum(4,5)是可折叠的常量,直接在编译期算出结果,和GCC的行为一致。
内容的提问来源于stack exchange,提问作者TupleCats
相关产品推荐
相关产品推荐

