C++;静态变量初始化顺序 1)如果我没有错,C++标准保证单个翻译单元中的静态变量按照定义顺序初始化。我对以下代码片段感到困惑: extern int n; int k = n; int n = 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;

C++;静态变量初始化顺序 1)如果我没有错,C++标准保证单个翻译单元中的静态变量按照定义顺序初始化。我对以下代码片段感到困惑: extern int n; int k = n; int n = 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;,c++,initialization,global-variables,static-members,static-variables,C++,Initialization,Global Variables,Static Members,Static Variables,extern是声明,而不是定义,因此k是在n之前定义的,但是GCC、Clang和MSVC都显示了在全局变量初始化之后k==2。对我来说,k如何在int k=n之后分配2是不清楚的,因为n尚未在该点初始化,其值必须为零 如果我们将最后一行更改为: int n = func(); 如果func()是非constexpr,那么k将被赋值为零,正如我所期望的那样。那么,全局变量在编译时的初始化会改变初始化的顺序吗 2) 下面是另一个代码片段: extern int n; int k = n; int

extern
是声明,而不是定义,因此
k
是在
n
之前定义的,但是GCC、Clang和MSVC都显示了在全局变量初始化之后
k==2
。对我来说,
k
如何在
int k=n之后分配2是不清楚的,因为
n
尚未在该点初始化,其值必须为零

如果我们将最后一行更改为:

int n = func();
如果
func()
是非constexpr,那么
k
将被赋值为零,正如我所期望的那样。那么,全局变量在编译时的初始化会改变初始化的顺序吗

2) 下面是另一个代码片段:

extern int n;
int k = n;
int n = 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?

第一个场景在[basic.start.init]/2中有很好的定义:

具有静态存储持续时间(3.7.1)或线程存储持续时间(3.7.2)的变量应在进行任何其他初始化之前进行零初始化(8.5)

执行常量初始化:

  • 如果在具有静态或线程存储持续时间的引用的初始值设定项中出现的每个完整表达式(包括隐式转换)是一个常量表达式(5.19),并且该引用绑定到指定具有静态存储持续时间的对象的左值或临时值(见12.2)
  • 如果具有静态或线程存储持续时间的对象由构造函数调用初始化,如果构造函数是
    constexpr
    构造函数,如果所有构造函数参数都是常量表达式(包括转换),如果函数调用替换后(7.1.5),对于非静态数据成员,mem初始值设定项和大括号或相等初始值设定项中的每个构造函数调用和完整表达式都是常量表达式
  • 如果具有静态或线程存储持续时间的对象未通过构造函数调用初始化,并且其初始值设定项中出现的每个完整表达式都是常量表达式
零初始化和常量初始化统称为静态初始化;所有其他初始化都是动态初始化静态初始化应在任何动态初始化发生之前执行。(…)

(强调矿山)

这段相当长的文章的结论是

int n = 2;
是静态初始化,而

int k = n;
动态初始化(因为
n
不是一个常量表达式),因此
n
k
之前初始化,即使它出现在代码的后面

同样的逻辑也适用于
Base::static_构造函数
示例——因为
Base::static_构造函数
的构造函数不是
constepr
Base::constructor
动态初始化的,而
Base::i
静态初始化的。因此,
Base::i
的初始化在
Base::constructor
初始化之前进行

另一方面,第二个案例

int n = func();
将您置于未指定行为的范围内,[basic.start.init]/3中非常明确地提到了这一点:

允许实现将具有静态存储持续时间的非局部变量的初始化作为静态初始化来执行,即使不需要以静态方式进行此类初始化,前提是

  • 初始化的动态版本在初始化之前不会更改命名空间范围的任何其他对象的值,并且
  • 如果所有不需要静态初始化的变量都是动态初始化的,则初始化的静态版本会在初始化变量中生成与动态初始化相同的值
[注:因此,如果对象
obj1
的初始化指的是命名空间范围的对象
obj2
,该对象可能需要动态初始化,并且稍后在同一翻译单元中定义,则未指定使用的
obj2
的值是否将是完全初始化的
obj2
的值>(因为
obj2
已静态初始化)或将是
obj2
的值,仅初始化为零。例如

inline double fd() { return 1.0; }
extern double d1;
double d2 = d1;     // unspecified:
                    // may be statically initialized to 0.0 or
                    // dynamically initialized to 0.0 if d1 is
                    // dynamically initialized, or 1.0 otherwise
double d1 = fd();   // may be initialized statically or dynamically to 1.0
--[完注]


在这两种情况下,
=2
是编译时常量,在运行时根本不会执行。如果将
=2
替换为
=f(2)
,如果
f
不是const-expr,那么我希望运行时初始化按照您期望的顺序进行。感谢您详尽的回答。但是如果
static\u构造函数
的构造函数是
constepr
,那该怎么办?它将是静态初始化,
I=1;
语句必须在执行之前执行
int Base::i=2;
,但我仍然看到
i==1
constexpr
构造函数的主体不能包含赋值(根据[dcl.constexpr]/4)。如果您只是将
constexpr
放在构造函数声明之前,编译器应该拒绝上面的代码,gcc和clang都这样做。我看到[dcl.constexpr]/4指的是[dcl.constexpr]/3,它又包含关于静态存储持续时间变量的唯一语句:“……constexpr函数的定义应满足以下约束:。。。