主流C++编译器对即时函数有效性判定的分歧咨询
C++ consteval即时函数的编译器行为分歧解析
在测试C的consteval即时函数特性时,发现Clang 15、GCC 12.2、MSVC 19.33等主流编译器的编译结果存在显著分歧。根据C标准中核心常量表达式的规则,原本预期所有测试用例都会触发编译错误,但实际各编译器对即时函数合法性的判定差异极大。
本文将明确:这些测试用例是否都应报错,还是其中部分属于合法代码?
测试用例代码
// #1 | Clang: 报错 | GCC: 通过 | MSVC: 报错 []() consteval { int i; +i; }(); // #3 | Clang: 报错 | GCC: 通过 | MSVC: 报错 []() consteval { 1 / 0; }(); // #4 | Clang: 报错 | GCC: 通过 | MSVC: 报错 []() consteval { 1 << -1; }(); // #5 | Clang: 报错 | GCC: 通过 | MSVC: 报错 []() consteval { 1 >> -1; }(); // #6 | Clang: 报错 | GCC: 通过 | MSVC: 通过 []() consteval { static_assert(sizeof(int) == 4); -(1 << 31); }(); // #7 | Clang: 报错 | GCC: 通过 | MSVC: 报错 []() consteval { union { int i; } u; +u.i; }(); // #8 | Clang: 报错 | GCC: 通过 | MSVC: 报错 []() consteval { union { bool b; int i; } u{}; +u.i; }(); // #9 | Clang: 报错 | GCC: 通过 | MSVC: 报错 []() consteval { int a[1]; +a[1]; }(); // #10 | Clang: 报错 | GCC: 通过 | MSVC: 报错 []() consteval { int* p; +*p; }(); // #16 | Clang: 报错 | GCC: 通过 | MSVC: 报错 []() consteval { auto p = new int; delete p; +*p; }(); // #17 | Clang: 报错 | GCC: 通过 | MSVC: 报错 []() consteval { int* p; { int i; p = &i; } +*p; }(); // #18 | Clang: 报错 | GCC: 报错 | MSVC: 通过 []() consteval { int const c{}; const_cast<int&>(c) = 0; }(); // #19 | Clang: 报错 | GCC: 通过 | MSVC: 报错 []() consteval { +*static_cast<int*>(nullptr); }(); // #20 | Clang: 报错 | GCC: 通过 | MSVC: 通过 []() consteval { static_cast<int*>(static_cast<void*>(nullptr)); }(); // #21 | Clang: 通过 | GCC: 报错 | MSVC: 报错 []() consteval { *static_cast<void*>(nullptr); }(); // #22 | Clang: 报错 | GCC: 报错 | MSVC: 通过 []() consteval { reinterpret_cast<void*>(nullptr); }();
编译器测试结果概览
| 测试用例 | Clang | GCC | MSVC |
|---|---|---|---|
| #1 | 报错 | 通过 | 报错 |
| #3 | 报错 | 通过 | 报错 |
| #4 | 报错 | 通过 | 报错 |
| #5 | 报错 | 通过 | 报错 |
| #6 | 报错 | 通过 | 通过 |
| #7 | 报错 | 通过 | 报错 |
| #8 | 报错 | 通过 | 报错 |
| #9 | 报错 | 通过 | 报错 |
| #10 | 报错 | 通过 | 报错 |
| #16 | 报错 | 通过 | 报错 |
| #17 | 报错 | 通过 | 报错 |
| #18 | 报错 | 报错 | 通过 |
| #19 | 报错 | 通过 | 报错 |
| #20 | 报错 | 通过 | 通过 |
| #21 | 通过 | 报错 | 报错 |
| #22 | 报错 | 报错 | 通过 |
规则依据与用例合法性判定
根据C++标准,consteval函数的调用必须能生成核心常量表达式,即不能包含任何违反常量表达式规则的操作(如未定义行为、禁止的类型转换等)。以下是各用例的详细判定:
应触发编译错误的用例(共15个)
这些用例均包含违反核心常量表达式规则的操作,编译器必须报错,部分编译器的"通过"属于实现偏差:
- #1:使用未初始化的局部变量
i,属于未定义行为,违反常量表达式对变量初始化的要求。 - #3:执行
1/0除以零操作,明确属于未定义行为,常量表达式中严格禁止。 - #4/#5:移位操作的右操作数为负数,属于未定义行为,不符合常量表达式的合法性要求。
- #6:在
sizeof(int)==4的前提下,1<<31会导致有符号整数溢出(未定义行为),后续的取反操作也因此不合法。 - #7/#8:访问联合的未活跃成员(#7中未初始化联合的
i成员;#8中初始化了b成员却访问i),常量表达式中不允许访问联合的非活跃成员。 - #9:数组越界访问
a[1](数组仅含1个元素,索引0有效),属于未定义行为。 - #10:解引用未初始化的指针
p,未定义行为。 - #16:解引用已释放的指针,未定义行为。
- #17:解引用指向已销毁局部变量的指针,未定义行为。
- #18:通过
const_cast修改const对象,属于未定义行为,常量表达式明确禁止此类操作。 - #19:解引用空指针,未定义行为。
- #21:解引用
void*类型的空指针,未定义行为。 - #22:使用
reinterpret_cast进行类型转换,C++标准中核心常量表达式禁止此类转换(空指针转void*不在允许范围内)。
合法的用例(仅#20)
static_cast<int*>(static_cast<void*>(nullptr))仅为空指针的类型转换,并未进行解引用或其他非法操作。空指针的类型转换本身不属于未定义行为,符合核心常量表达式的规则,因此该用例是合法的,Clang报错属于实现偏差。
总结
除#20外,其余所有测试用例均违反C++核心常量表达式的规则,都应触发编译错误。各编译器的分歧源于对标准规则的实现程度不同,存在部分编译器的实现偏差。
内容的提问来源于stack exchange,提问作者303
相关产品推荐
相关产品推荐

