简体   繁体   中英

A bug with the nifty-counter idiom or an ill-formed static order fiasco?

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 object std::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 use std::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 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;
    }
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM