评估潜在常量表达式期间的未定义行为
Undefined behaviour during evaluation of a potential constant expression
考虑这个程序:
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
extern int i;
struct S {
S() {
if (i == 0) {
puts("Hello, world!");
exit(0);
}
}
};
S s;
int i = 1 + 2 * INT_MIN;
int main() { }
正如我对表达式求值的理解,这是一个严格一致的程序,它打印"Hello,world!",然后退出,并且从未实际求值i
:的初始化程序
3.6.2非局部变量的初始化[basic.start.init]
[…]
在进行任何其他初始化之前,具有静态存储持续时间(3.7.1)或线程存储持续时间的变量(3.7.2)应为零初始化(8.5)。
执行恒定初始化:
--[…]
--如果具有静态或线程存储持续时间的对象没有由构造函数调用初始化,并且如果其初始化器中出现的每个完整表达式都是常量表达式。
零初始化和常量初始化统称为静态初始化;所有其他初始化都是动态初始化。应在进行任何动态初始化之前进行静态初始化。具有静态存储持续时间的非局部变量的动态初始化是有序的或无序的。[…]在单个翻译单元中定义的有序初始化变量应按照其在翻译单元中的定义顺序进行初始化。[…]
5.19常量表达式[expr.const]
条件表达式是核心常量表达式,除非它涉及作为潜在评估的子表达式(3.2)[…]:
--[…]
--未在数学上定义或不在其类型的可表示值范围内的结果;
[…]
文字常量表达式是文字类型的prvalue核心常量表达式,但不是指针类型。[…]文字常量表达式、引用常量表达式和地址常量表达式统称为常量表达式。
因为表达式1 + 2 * INT_MIN
有符号整数溢出,所以它不是核心常量表达式,因此不是字面常量表达式,也因此不是常量表达式。由于i
的初始化程序不是常数表达式,因此执行动态初始化。s
的初始化也是动态的,因为它的定义先于i
,所以它的构造函数首先运行。此时,仅执行了零初始化,因此检查i == 0
应评估为真。
然而,GCC和clang一致认为i
可以静态初始化为1
。当这两个人达成一致时,我的经验是他们是正确的,所以我想知道。。。我的分析有什么地方不正确吗?
我认为这里发生的事情是1 + 2 * INT_MIN
在编译时被求值,而i
则在静态初始化期间被初始化。这在[basic.start.init]/3 中是允许的
允许实现使用静态存储执行非本地变量的初始化作为静态初始化的持续时间,即使这种初始化不需要静态完成,只要
初始化的动态版本不会更改命名空间的任何其他对象的值初始化前的范围,以及
初始化的静态版本在初始化的变量中产生的值与如果所有不需要静态初始化的变量动态初始化。
因此,即使i
在常量初始化期间不需要初始化,它也可以在静态初始化期间初始化,而静态初始化仍在动态初始化之前。s
也是如此,但由于副作用,我认为编译器不可能做到这一点。
sftrabbit指出,您可以使初始化表达式任意复杂,而不是调用UB,因此i
实际上只在动态初始化期间初始化。例如:
int foobar()
{
return 42;
}
int i = foobar();
在两个编译器上打印Hello, world!
。
附带说明:为了查看1 + 2 * INT_MIN
由于有符号整数溢出而调用UB,可能需要对表达式进行求值。这可能会在初始化期间导致UB。
- 编译C++时未定义的引用
- 为什么当函数参数未定义为常量引用时存在无限递归?
- 未定义的对象(〔basic.life〕/8):为什么允许引用重新绑定(和常量修改)
- 对静态常量积分类型的未定义引用
- 使用静态常量初始化unique_ptr时出现未定义的引用错误
- 编译器在常量表达式中认为未定义的行为方面是否允许回旋余地?
- 评估潜在常量表达式期间的未定义行为
- 使用没有显式强制转换的常量调用未定义的行为
- 仅更改常量性的指针强制转换是否可以调用未定义的行为
- 对常量数组的未定义引用
- 未定义结构的静态常量成员
- C++ 对已定义常量的未定义引用
- 未定义的引用与嵌套类模板静态常量成员
- make_pair上具有静态常量的未定义引用
- C++和SDL 2-创建一个仅限常量的头:对常量::窗口的未定义引用
- 与 complex.h 一起使用时对静态常量双精度的未定义引用
- 试图修改const_cast类型,但动态分配的常量对象仍然未定义的行为
- C++未定义的引用...还有警告:已弃用从字符串常量到 'char*' 的转换
- 对静态常量变量的未定义引用collect2: ld返回1退出状态
- 帮助未定义和多个字符成为常量