问题
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!).
回答1:
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 type std::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 when std::cout
and friends are used during dynamic initialization, since that Init
definition will normally be earlier in the translation unit than any other non-local static storage object definition.
However, [basic.start.dynamic]/1 says
Dynamic initialization of a non-local variable with static storage duration is unordered if the variable is an implicitly or explicitly instantiated specialization, ....
So although the initialization of the std::ios_base::Init
object (effectively) defined in <iostream>
is ordered, the initialization of A<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 of A<int>::i
before the use of std::cout
:
struct Registry {
static int registerType(int type) {
static std::ios_base::Init force_init;
std::cout << "registering: " << type;
return type;
}
};
来源:https://stackoverflow.com/questions/59365123/a-bug-with-the-nifty-counter-idiom-or-an-ill-formed-static-order-fiasco