6

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.

3
  • 1
    I think that you are right and this code is correct.
    – rodrigo
    Commented Aug 2, 2018 at 13:54
  • I would avoid questionable code anyway
    – Slava
    Commented Aug 2, 2018 at 14:05
  • 5
    @Slava most of code with tag language-lawyer is questionable Commented Aug 2, 2018 at 14:07

2 Answers 2

6

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.

4
  • That logic suggests that a bare int i = i; will necessarily yield an int with value 0. DR2026 says that it is ill-formed. Commented Aug 2, 2018 at 14:05
  • @MartinBonner I believe it suggests that static int i = i; is zero. Commented Aug 2, 2018 at 14:06
  • @StoryTeller I think what confused me is that the behavior changed in c++14. From cppreference : "Constant initialization is performed [after (until C++14)][instead of (since C++14)] zero initialization" Commented Aug 2, 2018 at 14:08
  • 1
    @FrançoisAndrieux - Indeed. The validity of this code (and my answer) hinges on int i = s.i being dynamic initialization, making the C++14 change immaterial. If it's classified as anything other, then I think it's UB too. Commented Aug 2, 2018 at 14:10
2

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

5
  • This is the same thing I have been running through my head. If we know it was zeroed does the later default initialization really produce an indeterminate value or not. Commented Aug 2, 2018 at 13:58
  • The constexpr makes the DR dissimilar enough to be irrelevant. Commented Aug 2, 2018 at 14:05
  • I don't see a constexpr Commented Aug 2, 2018 at 14:06
  • constexpr int i = i; - in the DR itself Commented Aug 2, 2018 at 14:06
  • @StoryTeller Yes, I just realized that. D'oh! Commented Aug 2, 2018 at 14:07

Not the answer you're looking for? Browse other questions tagged or ask your own question.