6 c++ initialization compile-time-constant constant-expression c++11
我一直试图了解静态变量是如何初始化的。并注意到cppref和 enseignement处常量初始化和零初始化的顺序存在矛盾。
在cppref上它说:
常量初始化是在所有其他初始化之前执行的,而不是静态和线程局部(C++11 起)对象的零初始化。
而在enseignement中它说:
常量初始化是在静态和线程局部对象的零初始化之后以及所有其他初始化之前执行的。
正如您所看到的,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
}
Run Code Online (Sandbox Code Playgroud)
这是我到目前为止对初始化过程的理解:
现在根据上面(我的理解),上面的代码是如何工作的:
步骤1。
当控制流到达 的定义时,const int d它会看到初始化程序有一个S::c尚未初始化的变量(即 )。所以该语句const int d = 10 * S::c;是动态时(运行时)初始化。这意味着它只能在静态初始化之后发生。当然d这不是一个常量表达式。
第2步。
控制流到达变量 的定义const int S::c;。然而在这种情况下,初始化器是一个常量表达式,因此可以发生常量初始化。并且不需要零初始化。
步骤 3.
但请注意,我们(编译器)仍然没有初始化该变量d,因为它保留了初始化,因为它必须动态完成。所以现在这将发生并且d将得到值 50。但注意d仍然不是常量表达式,因此我们不能在需要常量表达式的地方使用它。
我对概念的分析/理解是否正确并且代码的行为是否如所描述的那样?
cppref-init和enseignement-init中常量初始化和零初始化的顺序也不同。
dfr*_*fri 10
如有疑问,请参阅标准。由于这个问题被标记为 C++11,因此我们将参考N3337。
具有静态存储持续时间([basic.stc.static])或线程存储持续时间([basic.stc.thread])的变量应在任何其他初始化发生之前进行零初始化 ([dcl.init]) 。
执行常量初始化:[...]
零初始化和常量初始化一起称为静态初始化;所有其他初始化都是动态初始化。静态初始化应在任何动态初始化发生之前执行。
因此,对于C++11标准,enseignement的描述是准确的。
常量初始化是在静态和线程局部对象的零初始化之后以及所有其他初始化之前执行的。
然而,根据CWG 2026,这被标记为缺陷:
CWG 同意在这些情况下应将常量初始化视为正在发生,而不是零初始化,从而导致声明格式不正确。
从 C++17 ( N4659 )开始,这一点发生了变化,从此由[basic.start.static]/2管辖:
[...] 如果具有静态或线程存储持续时间的变量或临时对象由实体的常量初始值设定项初始化,则执行常量初始化。如果不执行常量初始化,则具有静态存储持续时间或线程存储持续时间的变量将被初始化为零。零初始化和常量初始化一起称为静态初始化;所有其他初始化都是动态初始化。
但由于这不仅仅是新标准功能更新,而且是一个缺陷,因此它向后移植到旧标准,并且最终 cppreference 的描述对于 C++11 也是准确的(实际上一直回到 C++98),而enseignement 既没有考虑现代 C++ 标准,也没有考虑 DR CWG 2026。
我自己从未访问过 enseignement 的页面,但快速浏览了他们的参考页面后,它看起来像是 cppreference 本身的一个非常旧的版本,要么完全不属于cppreference,要么 cppreference 实际上是作为与 enseignement 作者的合作开始的。
如果没有标准本身,我认为 cppreference 是事实上的参考,并且鉴于 enseignement 的旧复制面食,我建议永远不要再次转向那些页面参考。事实上,我们可以访问有关常量初始化的实际最新 cppreference 页面,滚动到最底部并阅读:
缺陷报告
以下改变行为的缺陷报告追溯应用于之前发布的 C++ 标准。
[...]
- CWG 2026
- 适用于: C++98
- 已发布的行为:零初始化被指定为始终首先发生,甚至在常量初始化之前
- 正确的行为:如果常量 init 适用,则不会进行零初始化