c++ - 评估潜在常量表达式期间的未定义行为

标签 c++ c++11 language-lawyer

考虑这个程序:

#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 Initialization of non-local variables [basic.start.init]

[...]

Variables with static storage duration (3.7.1) or thread storage duration (3.7.2) shall be zero-initialized (8.5) before any other initialization takes place.

Constant initialization is performed:

-- [...]

-- if an object with static or thread storage duration is not initialized by a constructor call and if every full-expression that appears in its initializer is a constant expression.

Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. Static initialization shall be performed before any dynamic initialization takes place. Dynamic initialization of a non-local variable with static storage duration is either ordered or unordered. [...] Variables with ordered initialization defined within a single translation unit shall be initialized in the order of their definitions in the translation unit. [...]

5.19 Constant expressions [expr.const]

A conditional-expression is a core constant expression unless it involves one of the following as a potentially evaluated subexpression (3.2) [...]:

-- [...]

-- a result that is not mathematically defined or not in the range of representable values for its type;

[...]

A literal constant expression is a prvalue core constant expression of literal type, but not pointer type. [...] Collectively, literal constant expressions, reference constant expressions, and address constant expressions are called constant expressions.

因为表达式 1 + 2 * INT_MIN 有符号整数溢出,所以它不是核心常量表达式,因此不是文字常量表达式,因此不是常量表达式。因为 i 的初始化器不是常量表达式,所以执行动态初始化。 s 的初始化也是动态的,因为它的定义先于 i 的定义,所以它的构造函数先运行。那时,只执行了零初始化,因此检查 i == 0 应该评估为真。

但是,GCC 和 clang 同意 i 可以静态初始化为 1。当这两者一致时,我的经验是它们是正确的,所以我想知道...我的分析是否有任何部分不正确?

最佳答案

我认为这里发生的是 1 + 2 * INT_MIN 在编译时求值,而 i 在静态初始化期间初始化。这在 [basic.start.init]/3

中是允许的

An implementation is permitted to perform the initialization of a non-local variable with static storage duration as a static initialization even if such initialization is not required to be done statically, provided that

  • the dynamic version of the initialization does not change the value of any other object of namespace scope prior to its initialization, and

  • the static version of the initialization produces the same value in the initialized variable as would be produced by the dynamic initialization if all variables not required to be initialized statically were initialized dynamically.

因此,尽管 i 不需要在常量初始化期间初始化,但它可以在静态初始化期间初始化,静态初始化仍在动态初始化之前。 s 也是如此,但我认为编译器不可能这样做,因为有副作用。


作为sftrabbit注意,您可以使初始化表达式任意复杂,而不是调用 UB,这样 i 确实只在动态初始化期间被初始化。例如:

int foobar()
{
    return 42;
}

int i = foobar();

在两个编译器上打印 Hello, world!


附带说明:为了看到 1 + 2 * INT_MIN 由于有符号整数溢出而调用 UB,可能需要计算表达式。这可能会在初始化期间导致 UB。

关于c++ - 评估潜在常量表达式期间的未定义行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20862774/

相关文章:

C++ 检索字符串的一部分

c++ - 'drv' 没有命名类型

C++ Windows 在数组中设置值导致程序崩溃

c++ - 是否每个循环都会评估基于 C++11 范围的 for 循环条件?

c++ - Clang 未知类名 'exception'

c++ - 了解 C++14 中有符号类型的按位左移

c++ - 好友函数 "non-lvalue in assignment"

c++ - 在具有外部链接的匿名命名空间中声明的实体示例

c++ - GCC 和 -W 转换

c++ - map::emplace 在什么时候创建对象?