1)如果我没有弄错,C ++标准保证单个翻译单元中的静态变量按其定义顺序初始化。我对以下代码片段感到困惑:
extern int n;
int k = n;
int n = 2;
extern int n;
是声明,而不是定义,所以k
是在n
之前定义的,但是GCC,Clang和MSVC都在初始化之后向我显示k == 2
全局变量。对我来说,目前还不清楚k
之后如何int k = n;
分配2,因为n
尚未在该点初始化,其值必须为零。
如果我们将最后一行更改为:
int n = func();
其中func()
是非constexpr,然后k
将被指定为零,正如我所期望的那样。那么,在编译时初始化全局变量会改变初始化的顺序吗?
2)这是另一个代码片段:
class Base
{
public:
struct static_constructor
{
static_constructor()
{
i = 1;
}
};
static static_constructor constructor;
static int i;
};
Base::static_constructor Base::constructor;
int Base::i = 2;
定义Base::constructor
时,将调用其构造函数,并执行i = 1
赋值。但此时Base::i
尚未定义,所以,请您解释一下此时会发生什么,为什么Base::i
最终等于1?
答案 0 :(得分:5)
第一个场景在[basic.start.init] / 2中明确定义:
具有静态存储持续时间(3.7.1)或线程存储持续时间(3.7.2)的变量在进行任何其他初始化之前应进行零初始化(8.5)。
执行常量初始化:
- 如果在具有静态或线程存储持续时间的引用的初始值设定项中出现的每个完整表达式(包括隐式转换)是常量表达式(5.19),并且引用绑定到指定具有静态存储持续时间的对象的左值或暂时的(见12.2);
- 如果具有静态或线程存储持续时间的对象由构造函数调用初始化,如果构造函数是
constexpr
构造函数,如果所有构造函数参数都是常量表达式(包括转换),并且在函数调用替换之后(7.1.5),对于非静态数据成员, mem-initializers 和 brace-or-equal-initializers 中的每个构造函数调用和完整表达式都是恒定表达;- 如果构造函数调用未初始化具有静态或线程存储持续时间的对象,并且其初始化程序中出现的每个完整表达式都是常量表达式。
一起,零初始化和常量初始化称为静态初始化;所有其他初始化都是动态初始化。 静态初始化应在任何动态初始化之前执行。(...)
(强调我的)
这段相当冗长的段落的结果是
int n = 2;
是静态初始化,而
int k = n;
是动态初始化(因为n
不是常量表达式),因此n
在k
之前初始化,即使它稍后出现在代码中
同样的逻辑适用于Base::static_constructor
示例 - 因为Base::static_constructor
的构造函数不是constexpr
,Base::constructor
是动态初始化,而Base::i
静态初始化。因此Base::i
的初始化发生在Base::constructor
初始化之前。
另一方面,第二种情况是
int n = func();
将你直接置于未指明行为的领域,并在[basic.start.init] / 3中明确提到:
允许执行非静态存储持续时间的非局部变量的初始化作为静态初始化,即使这种初始化不需要静态完成,只要
- 初始化的动态版本在初始化之前不会更改命名空间作用域的任何其他对象的值,并且
- 初始化的静态版本在初始化变量中产生的值与动态初始化产生的值相同,如果所有不需要静态初始化的变量都是动态初始化的。
[注意:因此,如果对象
obj1
的初始化引用了命名空间范围的对象obj2
,可能需要动态初始化并在后面定义翻译单元,未指定所使用的obj2
的值是否为完全初始化obj2
的值(因为obj2
已静态初始化)或将是{{1}的值只是零初始化。例如,obj2
- 结束记录]