Static variables initialisation order
Asked Answered
D

7

73

C++ guarantees that variables in a compilation unit (.cpp file) are initialised in order of declaration. For number of compilation units this rule works for each one separately (I mean static variables outside of classes).

But, the order of initialization of variables, is undefined across different compilation units.

Where can I see some explanations about this order for gcc and MSVC (I know that relying on that is a very bad idea - it is just to understand the problems that we may have with legacy code when moving to new GCC major and different OS)?

Distributive answered 17/10, 2008 at 6:43 Comment(0)
S
82

As you say the order is undefined across different compilation units.

Within the same compilation unit the order is well defined: The same order as definition.

This is because this is not resolved at the language level but at the linker level. So you really need to check out the linker documentation. Though I really doubt this will help in any useful way.

For gcc: Check out ld

I have found that even changing the order of objects files being linked can change the initialization order. So it is not just your linker that you need to worry about, but how the linker is invoked by your build system. Even try to solve the problem is practically a non starter.

This is generally only a problem when initializing globals that reference each other during their own initialization (so only affects objects with constructors).

There are techniques to get around the problem.

  • Lazy initialization.
  • Schwarz Counter
  • Put all complex global variables inside the same compilation unit.

  • Note 1: globals:
    Used loosely to refer to static storage duration variables that are potentially initialized before main().
  • Note 2: Potentially
    In the general case we expect static storage duration variables to be initialized before main, but the compiler is allowed to defer initialization in some situations (the rules are complex see standard for details).
Sanson answered 17/10, 2008 at 7:23 Comment(9)
My own preference goes for all globals in the same compilation unit... :-)Nexus
Preferably one would not need globals at all.Sanson
You both right but unfortunately this was unknown to generations of programmers who wrote tons of libraries and 3rd party code we got to use...Distributive
+1 to this answer, espeically for the part of putting all global variables in the same compilation unitWrongdoing
@LokiAstari: I always thought that the order of initialisation of global variables in the same compilation unit was the same order as definition. But today I was bitten by my compiler, which did not do it, and I was surprised by this SO answer, pointing at a specific sequence between static initialisation first, then dynamic initialisation. 5 years later, can you comment on the validity of that ? (I know, I am asking a lot here, but I feel really confused)Autochthon
@AdN: Yes there is a difference between static and dynamic initialization. Static happens before dynamic (because it is done at compile time and baked into the underlying assembly segments (BSS block etc)). When people talk about initialization order we are only referring the to the dynamic part (the part that has to execute code at run time to be initialized (The C++11 constexpt is basically another compiler time constant)). This should not cause any of the arguments about this to change or cause something to bite you. Can you start a question so we can explore in more detail.Sanson
@MartinYork The question isn't about global variables, it's about static variables, which could equally be at namespace or class scope.Antietam
@MartinYork Yes, I am referring to your mention of "This is generally only a problem when initializing global that reference each other" and "Preferably one would not need globals at all"... I believe "global" is a misleading term here as the question is about static initialisation, and this isn't the same thing as global, which describes an object's scope. Basically, all globals have static storage duration, but not all static objects have global scope.Antietam
@jb Updated with a note within the text.Sanson
C
22

I expect the constructor order between modules is mainly a function of what order you pass the objects to the linker.

However, GCC does let you use init_priority to explicitly specify the ordering for global ctors:

class Thingy
{
public:
    Thingy(char*p) {printf(p);}
};

Thingy a("A");
Thingy b("B");
Thingy c("C");

outputs 'ABC' as you'd expect, but

Thingy a __attribute__((init_priority(300))) ("A");
Thingy b __attribute__((init_priority(200))) ("B");
Thingy c __attribute__((init_priority(400))) ("C");

outputs 'BAC'.

Carnap answered 17/10, 2008 at 7:30 Comment(5)
A new explicitly specify link that works for now.Induration
I used this attribute but gcc gives, "warning: requested init_priority is reserved for internal use". Being a warning I was still allowed to do this. Is there another way of setting initialization priority?Felid
@Andrew - You should not use init, fini or init_priority. Instead, use the constructor attribute. There's also a destructor attribute. You may need to use init and fini on other compilers and platforms, but for GCC, you don't use it.Also see "Clarification of attribute init_priority" on the GCC mailing list.Bottomless
@Bottomless constructor and destructor are not relevant: they exist to force certain functions to be called before and after main() respectively. They aren't anything to do with the initialisation order of particular objects, for which init_priority is precisely the correct tool. The warning Andrew saw was probably because he used a value not "bounded between 101 and 65535 inclusive" as stipulated in the docs for init_priority; one might thus conclude that values outwith that range are reserved, as the warning clearly said. I don't think either of these things are relevant to the question.Replete
...I also don't think relying on non-Standard, compiler-specific attributes like this is good practice. But hey, it's nice to know about the options. The other idea, that changing the order in which translation units are passed to the linker will change the order of dynamic initialisation among them, seems like an unfounded recipe for abysmally brittle code.Replete
F
22

Since you already know that you shouldn't rely on this information unless absolutely necessary, here it comes. My general observation across various toolchains (MSVC, gcc/ld, clang/llvm, etc) is that the order in which your object files are passed to the linker is the order in which they will be initialized.

There are exceptions to this, and I do not claim to all of them, but here are the ones I ran into myself:

1) GCC versions prior to 4.7 actually initialize in the reverse order of the link line. This ticket in GCC is when the change happened, and it broke a lot of programs that depended on initialization order (including mine!).

2) In GCC and Clang, usage of constructor function priority can alter the initialization order. Note that this only applies to functions that are declared to be "constructors" (i.e. they should be run just like a global object constructor would be). I have tried using priorities like this and found that even with highest priority on a constructor function, all constructors without priority (e.g. normal global objects, constructor functions without priority) will be initialized first. In other words, the priority is only relative to other functions with priorities, but the real first class citizens are those without priority. To make it worse, this rule is effectively the opposite in GCC prior to 4.7 due to point (1) above.

3) On Windows, there is a very neat and useful shared-library (DLL) entry-point function called DllMain(), which if defined, will run with parameter "fdwReason" equal to DLL_PROCESS_ATTACH directly after all global data has been initialized and before the consuming application has a chance to call any functions on the DLL. This is extremely useful in some cases, and there absolutely is not analogous behavior to this on other platforms with GCC or Clang with C or C++. The closest you will find is making a constructor function with priority (see above point (2)), which absolutely is not the same thing and won't work for many of the use cases that DllMain() works for.

4) If you are using CMake to generate your build systems, which I often do, I have found that the order of the input source files will be the order of their resultant object files given to the linker. However, often times your application/DLL is also linking in other libraries, in which case those libraries will be on the link line after your input source files. If you are looking to have one of your global objects be the very first one to initialize, then you are in luck and your can put the source file containing that object to be the first in the list of source files. However, if you are looking to have one be the very last one to initialize (which can effectively replicate DllMain() behavior!) then you can make a call to add_library() with that one source file to produce a static library, and add the resulting static library as the very last link dependency in your target_link_libraries() call for your application/DLL. Be wary that your global object may get optimized out in this case and you can use the --whole-archive flag to force the linker not to remove unused symbols for that specific tiny archive file.

Closing Tip

To absolutely know the resulting initialization order of your linked application/shared-library, pass --print-map to ld linker and grep for .init_array (or in GCC prior to 4.7, grep for .ctors). Every global constructor will be printed in the order that it will get initialized, and remember that the order is opposite in GCC prior to 4.7 (see point (1) above).

The motivating factor for writing this answer is that I needed to know this information, had no other choice but to rely on initialization order, and found only sparse bits of this information throughout other SO posts and internet forums. Most of it was learned through much experimentation, and I hope that this saves some people the time of doing that!

Filament answered 12/9, 2016 at 0:53 Comment(3)
"In other words, the priority is only relative to other functions with priorities, but the real first class citizens are those without priority." wow that is a terrible design choiceKora
I'm not actually sure it works that way anymore, I had some ~GCC9 code that seemed to be fixed by setting init_priority(101) on just one var which according to this should have no effectKora
@JosephGarvin possibly, though the documentation is still ambiguous about this: gcc.gnu.org/onlinedocs/gcc/…, and this open item about the ambiguity is not resolved or disputed: gcc.gnu.org/bugzilla/show_bug.cgi?id=65115 I haven't tested this functionality for quite a while, but I would be interested to see evidence of the behavior changing and what version that came in, such that I can update this answer to reflect it.Filament
H
4

http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.12 - this link moves around. this one is more stable but you will have to look around for it.

edit: osgx supplied a better link.

Hemo answered 17/10, 2008 at 7:41 Comment(1)
There is copy in web archive: http://web.archive.org/web/20080512011623/http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.12; "[10.12] What's the "static initialization order fiasco"?" section of C++ FAQ Lite by Marshall Cline. Similar section is in isocpp.org/wiki/faq/ctorsScandian
I
3

A robust solution is to use a getter function that returns a reference to an static variable. A simple example is shown below, a complex variant in our SDG Controller middleware.

// Foo.h
class Foo {
 public:
  Foo() {}

  static bool insertIntoBar(int number);

 private:
  static std::vector<int>& getBar();
};

// Foo.cpp
std::vector<int>& Foo::getBar() {
  static std::vector<int> bar;
  return bar;
}

bool Foo::insertIntoBar(int number) {
  getBar().push_back(number);
  return true;
}

// A.h
class A {
 public:
  A() {}

 private:
  static bool a1;
};

// A.cpp
bool A::a1 = Foo::insertIntoBar(22);

The initialization would being with the only static member variable bool A::a1. This would then call Foo::insertIntoBar(22). This would then call Foo::getBar() in which the initialization of the static std::vector<int> variable would occur before returning a reference to the initialized object.

If the static std::vector<int> bar were placed directly as a member variable of the Foo class, there would be a possibility, depending on the naming ordering of the source files, that bar would be initialized after insertIntoBar() were called, thereby crashing the program.

If multiple static member variables would call insertIntoBar() during their initialization, the order would not be dependent on the names of the source files, i.e., random, but the std::vector<int> would be guaranteed to be initialized before any values be inserted into it.

Insomnia answered 14/7, 2020 at 13:28 Comment(2)
This is the "singleton" function model , which is a good fix for start-up, with only a little runtime overhead. The problem to be aware of with singletons is shut-down: If one singleton object creates another child singleton in it's runtime methods (after its own construction), that child singleton will be destroyed before the parent singleton. If the parent destructor calls on the child, it will fail horribly as the child is already destructed!Folkrock
Totally agree. This is mainly aimed at embedded devices with limited resources and where shutdown a power loss or reset() callInsomnia
S
1

In addition to Martin's comments, coming from a C background, I always think of static variables as part of the program executable, incorporated and allocated space in the data segment. Thus static variables can be thought of as being initialised as the program loads, prior to any code being executed. The exact order in which this happens can be ascertained by looking at the data segment of map file output by the linker, but for most intents and purposes the initialisation is simultaeneous.

Edit: Depending on construction order of static objects is liable to be non-portable and should probably be avoided.

Shipmate answered 17/10, 2008 at 7:38 Comment(4)
The problem occurs when you have C++ classes whos constructors have side-effects (say, they reference each other).Carnap
Personally, i try to avoid this where ever possible, as my experience of this (os possibly lack of knowledge) has not been good. Usually I either move the bulk of the construction to an Init function, called at startup, or change from static to a global pointer initialised on the heap at startup.Shipmate
@smacl : Of course, but then you must handle and Finalize function to deallocate the data, and handle the fact that sometimes both Init and Finalize are called multiple times, and sometimes, concurrently. The RAII idiom, here, combined by automatic initialization of globals in a DLL is quite welcomeNexus
There are separate segments for constant- and dynamically initialised static objects. The former are initialised before the latter and can be baked into the executable. The latter cannot, and only if they are within the same translation unit can one depend upon their order of initialisation (== that of definition). If they span different TUs, relying on their order is not 'probably liable' to be bad practice: it's definitely bad. :PReplete
R
0

If you really want to know the final order I would recommend you to create a class whose constructor logs the current timestamp and create several static instances of the class in each of your cpp files so that you could know the final order of initialization. Make sure to put some little time consuming operation in the constructor just so you don't get the same time stamp for each file.

Residuum answered 25/4, 2015 at 9:54 Comment(2)
Doing this would not teach anything useful, as the order is formally undefined, so learning how it incidentally orders on one linker on one day - with the aim of relying on the resulting non-knowledge - is a recipe for brittle code that falls apart on the next. I suppose it might be interesting for someone idly studying how a given linker does things, but how many of us do that?Replete
Also, with the invention of dynamic relinkers (a looong time ago), there is actually no great guarantee that the static construction order will be the same between runs.Folkrock

© 2022 - 2024 — McMap. All rights reserved.