1

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.

anc
  • 106
  • 4
  • 18
  • 2
    You are probably facing [static initialization order fiasco](https://en.cppreference.com/w/cpp/language/siof). – Amir Kirsh Jan 22 '21 at 09:18
  • But why would it happen just in my machine? – anc Jan 22 '21 at 10:47
  • 1
    @anc it could happen if your build system doesn't generate consistent link commands. It could also happen because threads are launched during static initialization. What happens when your machine launches a binary built on another machine? What happens when a different machine launches the binary you built? – Drew Dormann Jan 22 '21 at 16:32

2 Answers2

2

The static initialization order fiasco, from which you are suffering, occurs when an object in one translation unit may rely on the data from another unit that has not yet been, or is in the process of being, initialized.

Dynamic initialization of static members across translation-units are indeterminately sequenced with respect to all other translation-units. There is no guarantee to the order in which they get initialized, or even if they are on the same thread.

There are a few possible conditions I can think of that may result in you experiencing it while others are not. Factors like hardware, link-order, timing, etc. may all play a role. Some possible cases:

  1. Since dynamic-initialization is indeterminately sequenced and not guaranteed to even exist in the same thread, if initialization is multi-threaded on startup, then differing hardware may introduce different initialization timing (race-condition)

  2. It could just be that there is a difference in your development environment on your system. Something as small as having a library found in a different location may disrupt the library load-order, which may affect the order in which things are initialized.

  3. Assuming your system is performing parallel-compilation, it's possible that your hardware is compiling objects in a different order, which has affected the order these objects get linked in. The link-order may change initialization order -- which may cause you to experience an issue where someone else doesn't.

Ultimately, why you are experiencing this when others are not doesn't actually matter. Formally, you are experiencing undefined behavior, and how it behaves and who it discriminates against cannot be reasoned about.


Note: Without a lot more information about your system setup, the best that can be provided is guesswork as to why this is occurring.

Human-Compiler
  • 11,022
  • 1
  • 32
  • 59
2

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

Amir Kirsh
  • 12,564
  • 41
  • 74