关于 C++ 中常量初始化和零初始化顺序的矛盾定义

Contradictory definitions about the Order of Constant Initialization and Zero Initialization in C++

我一直在努力理解静态变量是如何初始化的。并注意到 cppref and enseignement.

处常量初始化和零初始化顺序的矛盾

cppref 它说:

Constant initialization is performed instead of zero initialization of the static and thread-local (since C++11) objects and before all other initialization.

而在 enseignement 它说:

Constant initialization is performed after zero initialization of the static and thread-local objects and before all other initialization.

正如您所见,cppref 使用“instead”,而第二个站点使用“after”。 两者中哪一个是正确的?也就是说,零初始化 总是 首先发生,然后如果可能的话第二个站点或反过来。

那里给出的例子如下:

#include <iostream>
#include <array>
 
struct S {
    static const int c;
};
const int d = 10 * S::c; // not a constant expression: S::c has no preceding
                         // initializer, this initialization happens after const
const int S::c = 5;      // constant initialization, guaranteed to happen first
int main()
{
    std::cout << "d = " << d << '\n';
    std::array<int, S::c> a1; // OK: S::c is a constant expression
//  std::array<int, d> a2;    // error: d is not a constant expression
}

这是我目前对初始化过程的理解:

  1. 首先发生静态初始化。这包括
  1. 动态初始化

现在根据上面的(我的理解)上面的代码是这样工作的:

步骤 1.

当控制流到达 const int d 的定义时,它发现初始化程序有一个尚未初始化的变量(即 S::c)。所以语句 const int d = 10 * S::c; 是一个动态时间(运行时)初始化。这意味着它只能在静态初始化之后发生。当然 d 不是常量表达式。

步骤 2.

控制流到达变量定义const int S::c;。然而,在这种情况下,初始化程序是一个常量表达式,因此可以进行常量初始化。而且不需要零初始化。

步骤 3.

但是请注意,我们(编译器)仍然没有初始化变量 d 因为它离开了初始化,因为它必须动态完成。所以现在这将发生并且 d 将获得值 50。但是请注意 d 仍然不是常量表达式,因此我们不能在需要常量表达式的地方使用它。

我的 analysis/understanding 概念是否正确并且代码的行为与描述的一样?

注:

常量初始化和零初始化的顺序在cppref-init and enseignement-init也不同。

如有疑问,请转向标准。由于这个问题是用 C++11 标记的,我们将参考 N3337.

[basic.start.init]/2:

Variables with static storage duration ([basic.stc.static]) or thread storage duration ([basic.stc.thread]) shall be zero-initialized ([dcl.init]) before any other initialization takes place.

Constant initialization is performed: [...]

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.

因此,对于C++11标准,enseignement的描述是准确的。

Constant initialization is performed after zero initialization of the static and thread-local objects and before all other initialization.

但是,根据 CWG 2026:

,这被标记为 缺陷

CWG agreed that constant initialization should be considered as happening instead of zero initialization in these cases, making the declarations ill-formed.

从 C++17 开始 (N4659) this was changed, and henceforth governed by [basic.start.static]/2:

[...] Constant initialization is performed if a variable or temporary object with static or thread storage duration is initialized by a constant initializer for the entity. If constant initialization is not performed, a variable with static storage duration or thread storage duration is zero-initialized. Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization.

但由于这不仅仅是新标准功能更新,而是一个缺陷,它向后移植到旧标准,最后 cppreference 的描述对于 C++11 也是准确的(实际上一直回到 C+ +98),而 enseignement 既没有考虑现代 C++ 标准,也没有考虑 DR CWG 2026。

我自己从未访问过 enseignement 的页面,但快速浏览了他们的参考页面后,它看起来像是一个非常旧的 cppreference 版本,完全没有归因于 cppreference,或 cppreference 实际上是与 enseignement 作者合作开始的。

缺乏标准本身,我认为 cppreference 是事实上的参考,并且考虑到 enseignement 的旧复制意大利面,我建议永远不要再转向那些页面参考。事实上,我们可以访问 the actual up-to-date cppreference page on constant initialization,滚动到最底部并阅读:

Defect reports

The following behavior-changing defect reports were applied retroactively to previously published C++ standards.

[...]

  • CWG 2026
    • Applied to: C++98
    • Behavior as published: zero-init was specified to always occur first, even before constant-init
    • Correct behavior: no zero-init if constant init applies