关于 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.
当控制流到达 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.
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
我一直在努力理解静态变量是如何初始化的。并注意到 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.
当控制流到达 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.
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