Search code examples
c++visual-studio-2015static-linkingclang-tidy

The 'cppcoreguidelines-interfaces-global-init' goes wrong just in a specific scenario


Given the cppcoreguidelines-interfaces-global-init, specifically "initializing non-local variable with non-const expression depending on uninitialized non-local variable", which is exemplified here, I have the following scenario:

  • My team consists of 4 dev
  • We all have the same environment: VS2015 enter image description here
  • Everybody has the same VS project options
  • Our hardware is slightly different.

Then, I found a local static like below where the warning above ends up on a bad initialization.

static int GlobalScopeBadInit1 = ExternGlobal;

So far, so good - this is a bad init which might go wrong and we need to fix it.

The problem is: why does it go wrong just in my machine? No matter how hard we try - DEBUG or RELEASE, it just happens in my machine. We already cleaned up and deleted the files on other dev's machines and the code above goes wrong 100% of the times in my machine and 0% of the times on another dev's. It doesn't happen on build machine either.

Does anybody know what could explain that behavior?

Thanks.


Solution

  • The issue that you are facing is caused by the static initialization order fiasco, which says in short that the order of construction and destruction of static and global variables across different compilation units is unspecified.

    The fact that in your case the problem occurs (i.e. order of initialization causes a problem) only in a certain environment and not in another, is exactly what unspecified order means: it is affected by the order of compilation and it may be affected by compiler optimizations and other considerations that may be related to the environment. It may even be initialized concurrently in different threads (see: C++ spec [stmt.dcl] and a reason for why and when that may happen at the proper section in the original working doc dealing with that issue).

    Is there a solution for the static initialization order fiasco?

    Yes, there are several possible solutions.


    The first solution may be a redesign.

    Redesign option 1 - change the code so you would not have more than a single global object.

    You may handle all the other "globals" inside the single actual global object. There are libraries which handle singletons in such a way, managing under the hood all singletons inside a single global SingletonManager. But since such a change may require quite a lot code changes, with the accompanied risks, you may need to consider the other options.

    Redesign option 2 - use static or global functions instead of global variables - once your globals are retrieved from a static or global functions like the below the order of initialization is solved:

    Boo& get_global_boo() {
        static Boo b(get_global_foo());
        return b;
    }
    
    Foo& get_global_foo() {
        static Foo f(42);
        return f;
    }
    

    You may compare this code example facing the initialization order fiasco to one which solves the issue with static methods. This approach is sometimes called "Meyers Singleton" on behalf of Scott Meyers who discussed this approach in his book "More Effective C++".

    More on that approach can be found here and here.

    Redesign option 3 - a more simple redesign approach would be to move all global variables to a single compilation unit. This approach requires less changes in your code and is probably less risky. But is not always possible.


    Another solution is to use compiler specific options for setting the order of initialization - managing manually the order of static and global initializations can be done in Visual Studio with init_seg - a Visual Studio specific pragma allowing the developer to control the order of initializations. See: MSDN documentation and this blog post.

    GCC has also its own attribute for that purpose - init_priority.


    Last option is the best but most complicated - you may follow the way the C++ library does the trick for std::cout, which is a global object, yet guaranteed to be initialized whenever you need it, even in global context. This is done with the nifty counter idiom. You may read more about this idiom in the C++ Idioms wiki and also at this SO question. Note that the nifty counter idiom is not a solution for all cases of std::cout usage in global context, and in quite rare cases there is a need to "help" it work correctly with a statement like: static std::ios_base::Init force_init; see this SO post on that.


    For an additional discussion of the issue see also: Static variables initialisation order