Consider the following C++ code example:
#include<iostream>
class B {
public:
int val;
B(int v):val(v){
std::cout<< val << "\n";
}
};
template<typename T>
class A {
public:
static B b;
};
template<typename T>
B A<T>::b = B(1);
int main() {
A<int>::b.val;
return 0;
}
On GCC 11.4.0, build with g++ main.cc -g
got output 1
.
On Clang 14.0.0, build with clang++ main.cc -g
got segfault
.
Ubuntu 22.04.4 LTS
Cannot understand the reason of such behavior, would be very grateful for any help.
The program has undefined behavior because you use std::cout
without having any guarantee that it is initialized.
The standard streams like std::cout
are not automatically initialized and usable. Instead the header <iostream>
behaves as if it declared a global static storage duration variable of type std::ios_base::Init
. When a variable of this type is initialized, it will initialize the standard streams and make them usable.
The initialization of the static storage duration variable A<int>::b
is dynamic initialization, because the constructor of B
is not constexpr
and even if it was constexpr
still, because it calls a non-constexpr
operator<<
. And because it is a non-local variable instantiated from a template, it has unordered dynamic initialization, meaning that its initialization is unordered with any other dynamic initialization of non-local variables.
Because the initialization of A<int>::b
is unordered with initialization of <iostream>
's std::ios_base::Init
instance, it may happen prior to the initialization of std::cout
.
To assure that the streams are initialized when you need to use them before main
is entered, you need to initialize a std::ios_base::Init
instance yourself:
B(int v):val(v){
std::ios_base::Init _;
std::cout<< val << "\n";
}
Or better, avoid global variables with dynamic initialization before main
and instead use local static storage duration variables which are initialized at their first point of use from main
. See e.g. the Meyers' singleton pattern.