The following code crashes with clang (version 5.0.0-3~16.04.1 on x86_64-pc-linux-gnu) but works fine with gcc (9.2.0).
struct Registry {
static int registerType(int type) {
std::cout << "registering: " << type;
return type;
}
};
template<typename T>
struct A {
static int i;
};
template<typename T>
int A<T>::i = Registry::registerType(9);
int main() {
std::cout << A<int>::i << std::endl;
}
The clang crash, is according to address sanitizer due to:
ASAN:DEADLYSIGNAL
=================================================================
==31334==ERROR: AddressSanitizer: SEGV on unknown address 0xffffffffffffffe8 (pc 0x7f5cc12b0bb6 bp 0x7ffdca3d1a20 sp 0x7ffdca3d19e0 T0)
==31334==The signal is caused by a READ memory access.
#0 0x7f5cc12b0bb5 in std::ostream::sentry::sentry(std::ostream&) /root/orig/gcc-9.2.0/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/ostream.tcc:48:31
#1 0x7f5cc12b11e6 in std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long) /root/orig/gcc-9.2.0/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/ostream_insert.h:82:39
#2 0x4197a7 in __cxx_global_var_init.1 (/tmp/1576534654.656283/a.out+0x4197a7)
#3 0x514eac in __libc_csu_init (/tmp/1576534654.656283/a.out+0x514eac)
#4 0x7f5cc02847be in __libc_start_main /build/glibc-Cl5G7W/glibc-2.23/csu/../csu/libc-start.c:247
#5 0x419858 in _start (/tmp/1576534654.656283/a.out+0x419858)
Is this a bug with the nifty-counter idiom in clang, or an example of an ill-formed static initialization order fiasco?
Edit
Following the accepted answer, the question can be rephrased to:
- Can it be that the global
ostream
objectstd::cout
is not properly initialized? - Is there a valid case in which the compiler is allowed not to have
std::cout
initialized, even though we included iostream and we usestd::cout
properly? - Is there a use case where crashing on an ordinary
cout << "foo"
is not a compiler bug?
To avoid the spoiler I would just hint that the answer is Yes. This can happen, but don't worry there is a workaround. To see more follow the accepted answer below.
Also following the accepted answer, the case in question can be narrowed to an even more basic scenario:
int foo() {
std::cout << "foo";
return 0;
}
template<typename T>
struct A {
static int i;
};
template<typename T>
int A<T>::i = foo();
int main() {
(void) A<int>::i;
}
that crashes on the said clang version (and as it seems, justifiably!).
The code unfortunately has unspecified behavior. The reason is similar to, if not the usual definition of, the Static Initialization Order Fiasco.
The object
std::cout
and other similar objects declared in<iostream>
may not be used before the first object of typestd::ios_base::Init
is initialized. Including<iostream>
defines (or acts as though it defines) a non-local object of that type with static storage duration ([iostream.objects.overview]/3). This takes care of the requirement in most cases, even whenstd::cout
and friends are used during dynamic initialization, since thatInit
definition will normally be earlier in the translation unit than any other non-local static storage object definition.However, [basic.start.dynamic]/1 says
So although the initialization of the
std::ios_base::Init
object (effectively) defined in<iostream>
is ordered, the initialization ofA<int>::i
is unordered, and therefore the two initializations are indeterminately sequenced. So we can't count on this code working.As @walnut mentioned in a comment, the code can be corrected by forcing another
std::ios_base::Init
object to be initialized during dynamic initialization ofA<int>::i
before the use ofstd::cout
: