5

1) If I'm not mistaken, C++ standard guarantees that static variables in a single translation unit are initialized in their definition order. And I'm confused about the following code fragment:

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

extern int n; is the declaration, not the definition, so k is defined before n, but GCC, Clang and MSVC all show me that k == 2 after the initialization of the global variables. For me it's unclear how can k be assigned 2 after int k = n;, because n is not initialized at that point yet and its value must be zero.

If we change the last line to:

int n = func();

where func() is non-constexpr, then k will be assigned zero, as I expect. So, do initialization of the global variables at the compile time change the order of the initialization?

2) Here is another code fragment:

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;

When Base::constructor is defined, its constructor is called, and i = 1 assignment is performed. But at this point Base::i is not defined yet, so, please, could you explain me what happens at this point and why is Base::i equal to 1 in the end?

undermind
  • 1,779
  • 13
  • 33
  • In both cases, `= 2` is compile-time constant and is not executed at runtime at all. If you replace `= 2` with `=f(2)`, where `f` is not const-expr, then I would expect the runtime initialization to flow in the order you are expecting. – VoidStar Mar 28 '15 at 01:04

1 Answers1

7

The first scenario is well-defined in [basic.start.init]/2:

Variables with static storage duration (3.7.1) or thread storage duration (3.7.2) shall be zero-initialized (8.5) before any other initialization takes place.

Constant initialization is performed:

  • if each full-expression (including implicit conversions) that appears in the initializer of a reference with static or thread storage duration is a constant expression (5.19) and the reference is bound to an lvalue designating an object with static storage duration or to a temporary (see 12.2);
  • if an object with static or thread storage duration is initialized by a constructor call, if the constructor is a constexpr constructor, if all constructor arguments are constant expressions (including conversions), and if, after function invocation substitution (7.1.5), every constructor call and full-expression in the mem-initializers and in the brace-or-equal-initializers for non-static data members is a constant expression;
  • if an object with static or thread storage duration is not initialized by a constructor call and if every full-expression that appears in its initializer is a constant expression.

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. (...)

(Emphasis mine)

The upshot of this fairly lengthy paragraph is that

int n = 2;

is static initialization, while

int k = n;

is dynamic initialization (because n is not a constant expression), and therefore n is initialized before k even if it appears later in the code.

The same logic applies in the case of the Base::static_constructor example -- because the constructor of Base::static_constructor is not constexpr, Base::constructor is dynamically initialized, whereas Base::i is statically initialized. The initialization of Base::i therefore takes place before the initialization of Base::constructor.

On the other hand, the second case with

int n = func();

puts you squarely in the territory of unspecified behavior, and it is quite explicitly mentioned in [basic.start.init]/3:

An implementation is permitted to perform the initialization of a non-local variable with static storage duration as a static initialization even if such initialization is not required to be done statically, provided that

  • the dynamic version of the initialization does not change the value of any other object of namespace scope prior to its initialization, and
  • the static version of the initialization produces the same value in the initialized variable as would be produced by the dynamic initialization if all variables not required to be initialized statically were initialized dynamically.

[Note: As a consequence, if the initialization of an object obj1 refers to an object obj2 of namespace scope potentially requiring dynamic initialization and defined later in the same translation unit, it is unspecified whether the value of obj2 used will be the value of the fully initialized obj2 (because obj2 was statically initialized) or will be the value of obj2 merely zero-initialized. For example,

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

-- end note]

Wintermute
  • 42,983
  • 5
  • 77
  • 80
  • Thank you for your exhaustive answer. But what if `static_constructor`'s constructor is `constexpr`? It will be static initialization and `i = 1;` statement must be executed before `int Base::i = 2;`, but I still see `i == 1`. – undermind Mar 28 '15 at 10:42
  • The body of a `constexpr` constructor cannot contain an assignment (as per [dcl.constexpr]/4). The compiler should reject the above code if you just put `constexpr` in front of the constructor declaration, and both gcc and clang do so. – Wintermute Mar 28 '15 at 10:51
  • I see that [dcl.constexpr]/4 refers to [dcl.constexpr]/3, which in its turn contains the only statement concerning static storage duration variables: "...The definition of a constexpr function shall satisfy the following constraints: ... its function-body shall be = delete, = default, or a compound-statement that does not contain... a definition of a variable of non-literal type or of static or thread storage duration or for which no initialization is performed." `i = 1;` is not a definition, as I understand, so why is this code rejected? – undermind Mar 28 '15 at 11:05
  • Yes, you're right about compilers, I used some GCC 5.0.0 experimental compiler, that accepted my code, but other compilers did not. – undermind Mar 28 '15 at 11:08
  • Ah -- I was arguing from C++11, where it is more explicit. In C++14, [expr.const]/2,4 prohibit assignments in constant expressions, but interestingly [dcl.constexpr] does not prohibit them in `constexpr` functions (that I can see). Either way, though, a `constexpr` function with an assignment in it would not yield a constant expression (I have no idea in what context such a function could make sense), and then we end up with dynamic initialization again. – Wintermute Mar 28 '15 at 11:28
  • Clarification: assignments to global variables are prohibited in constant expressions. It is fine with function-local variables, to allow loops in constexpr functions (there's an example in [dcl.constexpr]/3). I'm not entirely convinced that `constexpr` functions that can't yield constant expressions are valid, but I haven't seen anything that explicitly prohibits them yet. Having them would make no sense whatsoever, though. – Wintermute Mar 28 '15 at 11:33
  • 2
    I see, according to [dcl.constexpr]/5: "if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression... the program is ill-formed; no diagnostic required"; the definition of a core constant expression: "A conditional-expression _e_ is a core constant expression unless the evaluation of _e_... would evaluate... modification of an object, unless it is applied to a non-volatile lvalue... whose lifetime began within the evaluation of _e_". So, constexpr functions modifying the global variables are ill-formed. – undermind Mar 28 '15 at 12:16
  • And, I hope, the last question on today :) : what if `int Base::i = 2;` line is replaced with `int Base::i = func();`, where `func()` can't be calculated at compile time? In this case both global variables initializations are dynamic and `Base::constructor` is initialized first. In its constructor a value is assigned to `Base::i`, which is not yet defined, only declared, and memory for it is not yet allocated (as I understand). What does compiler do at this point? Why is such code correct (if it is)? – undermind Mar 28 '15 at 12:40
  • When `Base::constructor` is executed, `i` is zero-initialized as per [basic.start.init]/2. Apart from that, you're correct -- [basic.start.init]/3 does not apply because the static version of the initialization of `Base::i` (if otherwise possible) would not produce the same result, so both are initialized dynamically and in the order they appear. Mind you, this is not a corner of the standard where I'd rely on that sort of fine detail. – Wintermute Mar 28 '15 at 12:49