In-class initialization from static member of the same type

269 views Asked by At

Is the following code valid, e.g. doesn't bring undefined behaviour?

struct S
{
    int i = s.i;
    static S s;
};

S S::s;

int main()
{
    S a;  // a.i = 0
    S::s.i = 42;
    S b;  // b.i = 42
}

As far as I know all variables with static storage duration are zero initialized. Hence s.i is 0 on S::s creation, and all is good. But maybe I'm missing something.

2

There are 2 answers

4
StoryTeller - Unslander Monica On BEST ANSWER

I would argue it's well defined.

[class.static.data]/6

Static data members are initialized and destroyed exactly like non-local variables.

[basic.start.static]/2 (emphasis mine)

A constant initializer for a variable or temporary object o is an initializer whose full-expression is a constant expression, except that if o is an object, such an initializer may also invoke constexpr constructors for o and its subobjects even if those objects are of non-literal class types. [ Note: Such a class may have a non-trivial destructor.  — end note ] 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. All static initialization strongly happens before ([intro.races]) any dynamic initialization. [ Note: The dynamic initialization of non-local variables is described in [basic.start.dynamic]; that of local static variables is described in [stmt.dcl].  — end note ]

[dcl.init]/6 (emphasis mine)

To zero-initialize an object or reference of type T means:

  • if T is a scalar type, the object is initialized to the value obtained by converting the integer literal 0 (zero) to T;
  • if T is a (possibly cv-qualified) non-union class type, each non-static data member, each non-virtual base class subobject, and, if the object is not a base class subobject, each virtual base class subobject is zero-initialized and padding is initialized to zero bits;
  • if T is a (possibly cv-qualified) union type, the object's first non-static named data member is zero-initialized and padding is initialized to zero bits;
  • if T is an array type, each element is zero-initialized;
  • if T is a reference type, no initialization is performed.

Because int i = s.i; means s.i goes through dynamic initialization, it's guaranteed to be zero initialized beforehand. So when it'll be used to initialize itself later, it's value won't be indeterminate. A 0 is to be expected.

5
Martin Bonner supports Monica On

You are missing something. Variables with static storage duration are zeroed, and then their constructor is called.

What I can't quite tell is whether the initialization of S.i with the value of S.i is undefined behaviour (because S.i is not initialized at this point) or not (because it must be zero).


Edit: The code in Defect Report 2026 is very similar in effect to this, and is declared to be ill-formed (which means the compiler must error). My suspicion is that the intention of the committee is that the OP's code is undefined behaviour.

Edit 2: The above DR refers to constexpr values. That probably changes things enough that is irrelevant.

Having said that: if you are relying on very careful reading of the standard to make your code legal, you are relying on the compiler author to have read it as carefully. You may be right, but that doesn't help in the short-term if the compiler author has misread and implemented something else (although hopefully, they will eventually fix the bug).