8

Following code

#include <iostream>

struct A {
    A() {
        std::cout << std::endl;
    }
};

struct B {
    static inline A a;
};

int main() {
}

succeeds after compiling with gcc, but crashes with segmentation fault after compiling with clang. Is the code not standard or is clang wrong?

https://godbolt.org/z/tEvfrW

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
tilin
  • 332
  • 1
  • 7
  • The `MVCC` result is same as that of `GCC`. Something odd with how `clang` processes `static inline A a;`? Also, I think [copy-elision](https://en.cppreference.com/w/cpp/language/copy_elision) will play a part in this (I may be wrong on this one though...) – kesarling He-Him Jul 05 '20 at 07:36
  • 5
    @molbdnilo, I guess we have [stronger guarantee](https://en.cppreference.com/w/cpp/io/ios_base/Init): *The header `` behaves as if it defines (directly or indirectly) an instance of `std::ios_base::Init` with static storage duration: this makes it safe to access the standard I/O streams in the constructors and destructors of static objects with ordered initialization (as long as `#include ` is included in the translation unit before these objects were defined).* If we make `B::a` not `inline`, it works. – Evg Jul 05 '20 at 07:45
  • What is the meaning of inlining an object? – kesarling He-Him Jul 05 '20 at 07:47
  • @d4rk4ng31 c++17 feature – tilin Jul 05 '20 at 07:48
  • 1
    Which version of Clang does it crash with? It works fine with `Apple clang version 11.0.0`. – John Zwinck Jul 05 '20 at 07:50
  • @d4rk4ng31 Making a variable inline means the same as for functions – its definition can exist in more than one translation unit, and must exist in every translation unit where it is odr-used. It is meaningless in this example, except for exposing an apparent bug in some versions of clang. – molbdnilo Jul 05 '20 at 07:51
  • @JohnZwinck, for example, `clang version 10.0.0-4ubuntu1`. – Evg Jul 05 '20 at 07:53

1 Answers1

7

Cppreference on std::ios_base::Init reads:

The header <iostream> behaves as if it defines (directly or indirectly) an instance of std::ios_base::Init with static storage duration: this makes it safe to access the standard I/O streams in the constructors and destructors of static objects with ordered initialization (as long as #include <iostream> is included in the translation unit before these objects were defined).

You do include <iostream> before B::a, but the initialization of B::a (with B::a being static inline variable) is not part of ordered initialization, so it can be initialized before std::ios_base::Init. It seems that Clang (some versions, at least) does exactly this. This is a valid behaviour.

The standard reads ([basic.start.dynamic]):

  1. Dynamic initialization of a non-local variable with static storage duration is unordered if the variable is an implicitly or explicitly instantiated specialization, is partially-ordered if the variable is an inline variable that is not an implicitly or explicitly instantiated specialization, and otherwise is ordered.

So, an initialization of instance of std::ios_base::Init is ordered, and initialization of B::a is partially-ordered.

  1. Dynamic initialization of non-local variables V and W with static storage duration are ordered as follows:

3.1. If V and W have ordered initialization and the definition of V is appearance-ordered before the definition of W, or if V has partially-ordered initialization, W does not have unordered initialization, and for every definition E of W there exists a definition D of V such that D is appearance-ordered before E, then ...

3.2. Otherwise, if the program starts a thread other than the main thread before either V or W is initialized, it is unspecified in which threads the initializations of V and W occur; the initializations are unsequenced if they occur in the same thread.

3.3. Otherwise, the initializations of V and W are indeterminately sequenced.

3.1 and 3.2 don't apply. So we have indeterminately sequenced initializations.

You can make B::a a non-inline static variable or somehow force std::ios_base::Init initialization before using std::cout, for example:

struct A {
    A() {
        std::cout << std::endl;
    }

    std::ios_base::Init init;
};
Evg
  • 25,259
  • 5
  • 41
  • 83