【问题标题】:C++ static variables initialization orderC++静态变量初始化顺序
【发布时间】:2015-06-01 11:18:20
【问题描述】:

1) 如果我没记错的话,C++ 标准保证单个翻译单元中的静态变量按其定义顺序进行初始化。我对以下代码片段感到困惑:

extern int n;
int k = n;
int n = 2;

extern int n;是声明,不是定义,所以k是在n之前定义的,但是GCC、Clang和MSVC都告诉我k == 2是在全局变量初始化之后。对我来说,不清楚如何在int k = n; 之后为k 分配2,因为n 那时还没有初始化,它的值必须为零。

如果我们把最后一行改成:

int n = func();

如果func() 是非常量表达式,那么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?

【问题讨论】:

  • 在这两种情况下,= 2 都是编译时常量,根本不会在运行时执行。如果您将= 2 替换为=f(2),其中f 不是const-expr,那么我希望运行时初始化按照您期望的顺序进行。

标签: c++ initialization global-variables static-members static-variables


【解决方案1】:

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

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

不断初始化

  • 如果出现在具有静态或线程存储持续时间的引用的初始化程序中的每个完整表达式(包括隐式转换)都是一个常量表达式 (5.19),并且该引用绑定到一个左值,该左值指定具有静态存储持续时间的对象或到一个临时的(见 12.2);
  • 如果具有静态或线程存储持续时间的对象由构造函数调用初始化,如果构造函数是 constexpr 构造函数,如果所有构造函数参数都是常量表达式(包括转换),并且如果在函数调用替换 (7.1 .5),对于非静态数据成员,mem-initializersbrace-or-equal-initializers 中的每个构造函数调用和完整表达式都是常量表达式;
  • 如果具有静态或线程存储持续时间的对象未通过构造函数调用进行初始化,并且出现在其初始化程序中的每个完整表达式都是常量表达式。

零初始化和常量初始化合称为静态初始化;所有其他初始化都是动态初始化静态初始化应在任何动态初始化发生之前执行。 (...)

(强调我的)

这个相当长的段落的结果是

int n = 2;

静态初始化,而

int k = n;

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

同样的逻辑也适用于Base::static_constructor 的例子——因为Base::static_constructor 的构造函数不是constexprBase::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

--结束注释]

【讨论】:

  • 感谢您详尽的回答。但是如果static_constructor 的构造函数是constexpr 呢?这将是静态初始化,i = 1; 语句必须在int Base::i = 2; 之前执行,但我仍然看到i == 1
  • constexpr 构造函数的主体不能包含赋值(根据 [dcl.constexpr]/4)。如果你只是将constexpr 放在构造函数声明的前面,编译器应该拒绝上面的代码,而 gcc 和 clang 都这样做。
  • 我看到 [dcl.constexpr]/4 指的是 [dcl.constexpr]/3,它又包含有关静态存储持续时间变量的唯一声明:“...... constexpr 函数应满足以下约束: ...其函数体应为 = delete、= default 或不包含...非文字类型或静态或线程变量的定义的复合语句存储持续时间或不执行初始化。” i = 1; 不是一个定义,据我所知,为什么这段代码被拒绝了?
  • 是的,关于编译器,你是对的,我使用了一些 GCC 5.0.0 实验性编译器,它接受了我的代码,但其他编译器没有。
  • 我明白了,根据 [dcl.constexpr]/5:“如果不存在参数值,那么函数或构造函数的调用可能是核心常量表达式的评估子表达式......程序格式错误;不需要诊断”;核心常量表达式的定义:“条件表达式 e 是核心常量表达式,除非 e... 的求值将求值... 对象的修改, 除非它应用于非易失性左值...其生命周期始于对 e" 的评估。因此,修改全局变量的 constexpr 函数格式不正确。
猜你喜欢
  • 2010-09-17
  • 2018-12-19
  • 1970-01-01
  • 2021-07-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多