考虑这个程序:
#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() { }
Run Code Online (Sandbox Code Playgroud)
正如我理解表达式的评估,这是一个严格符合的程序,打印"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();
Run Code Online (Sandbox Code Playgroud)
Hello, world!在两个编译器上打印.
作为旁注:为了看到1 + 2 * INT_MIN由于有符号整数溢出而调用UB,可能需要评估表达式.这可能会在初始化期间导致UB.