Introduction
Any developer sooner or later will stumble across the issue of the undefined order global and static objects are initialized at.
A not so uncommon example is when using a custom memory management system. Usually you want the memory management system to be initialized prior to any allocation which occurs and after all the allocated memory was freed again.
This is problematic, if global/static objects rely on memory allocations.
The wrong approach
Assume you would initialize the memory manager as the first call in your main()-function and shut it down as the last step prior to returning from main().
The issue you will end up with is that other global objects are initialized prior to your initialization call in main(). You might consider it being a solution to perform some implicit initialization of the memory manager. Besides this coming with added complexity and some (unavoidable) performance penalty, it won’t help with the issue that when you shut down the program, the corresponding destruction of these globals will happen after the main() function already returned and the memory manager was shut down.
You might think of handling this too then, but that won’t work (at least not in a sane/clean way) because your memory manager will certainly require some resources which need to be freed at shutdown.
How about atexit()?
So you might consider the alternative approach and use an atexit()-registered function (your shutdown function). This is however especially bad for a memory manager because:
- atexit()-registered functions are processed in LIFO order and so won’t change the behavior you faced above with calling the shutdown function last in your main() function
- atexit uses heap-allocated memory which you presumably directed through your memory manager
Let’s use a global
So the third idea comes to mind and put the initialization and termination handling of the memory manager in a global object’s constructor/destructor itself.
The problem you are facing here is the issue of how you’d be able to control that this particular global object is initialized before all other global objects and destroyed last.
The solution
A common approach to prevent problems caused by the undefined order is to stop using global and static objects altogether (f.e. by relying on pointers and defining an explicit initialization order in the app’s main() function). However, this approach is not always feasible and comes with certain drawbacks (which are outside the scope for this blog post). [2]
A different solution is provided in Visual Studio (with the MS CRT) by means of the “init_seg”-pragma which can be used to control the initialization order. [1]
To understand how this works, you should know that global objects are initialized as part of the CRT initialization. [3]
In particular, the CRT adds the initializers for all globals in the “.CRT$XCU” [4] linker section. The trick is now to use the “init_seg”-pragma to specify that the initialization of globals in the corresponding translation unit should go in a different section (i.e. one before the “.CRT$XCU” section but after “.CRT$XCA” [5]).
That can be done by adding the following pragma to the particular cpp file containing the global initialization:
#pragma init_seg(".CRT$XCT")
This ensures that your globals in the translation unit will be initialized prior to other globals of your application.
A word of warning
However, be careful with that approach and be aware that your global objects constructors will be called prior to other global objects (including potentially global objects used by the CRT itself!). [6]
Also bare in mind that this is kind of an advanced feature which is not too widely used and is (as far as the author is concerned) not an officially supported approach/functionality. That means that different CRT versions (even different flavors like debug vs. release runtime) can emit different behavior by putting initialization code in different sections. Your application might just work fine for years but suddenly stops working and experiences crashes (f.e. after a security update to the CRT was released or after you ported your application to a later VS version).
The second concern you need to be aware of are interactions with 3rd-party libraries. If you use different libraries these could also use the trick to put their own initialization related code in the CRT linker section and your code might then run after (or before) the other lib was initialized.
It’s therefore important to consider which section you put your initialization code in. In general it shouldn’t be a bad idea to put it into the “.CRT$XCT” section (i.e. closest reasonable section just before the “.CRT$XCU” section where other globals will be initialized in) rather than trying to put it in the earliest one (i.e. “.CRT$XCB”). That way you should be on the safer side with regards to a not yet completely initialized CRT which could cause quite a couple of sleepless nights tracing down some weird undefined behavior in your application.
On top of this, it’s also good practice to keep the constructor/destructor of such global objects as simple as possible and defer any initialization/termination code to be done as part of the normal program flow (i.e. during main()). This ensures that you are less likely to run into issues due to an incompletely initialized dependent global object (which could be part of the CRT or a dependent 3rd-party library).
Verifying whether you run into an issue with the global initialization order
If you run into a crash with the callstack pointing to the dynamic initializer list when starting your program which wasn’t present without the pragma statement, it probably means you did overlook such a global object dependency. To validate this, you can make use of the linker’s map output file and review which CRT-linker sections are used.
To do this, first you comment out the “init_seg”-pragma statement and then rebuild the program with the map output file. Using a text editor you should be able to locate the “.CRT$XC” sections at the top of the map file which could look like this:
1 2 3 4 5 6 7 |
[...] 0003:00000000 00000104H .CRT$XCA DATA 0003:00000104 00000104H .CRT$XCAA DATA 0003:00000208 00000104H .CRT$XCB DATA 0003:0000030c 00000104H .CRT$XCC DATA 0003:00000410 00000104H .CRT$XCZ DATA [...] |
These are sorted alphabetically and you’d see if there’s a section which unexpectedly comes before the section you put your global in. If so, simply change the section you use to a later one.
If you found this information interesting, you might also be interested in this follow-up blog post regarding further details related to the initialization order of globals.
References / Footnotes
[1] https://docs.microsoft.com/en-us/cpp/preprocessor/init-seg?view=vs-2017
[2] https://stackoverflow.com/questions/6939989/global-c-object-initialization#6940356
[3] https://docs.microsoft.com/en-us/cpp/c-runtime-library/crt-initialization?view=vs-2017
[4] To be precise the section name is actually .CRT with XCU being the section group.
[5] The XCA group specifies the __xc_a pointer which marks the start of the global initialization list and therefore no initialization should be put into that group.
[6] https://developercommunity.visualstudio.com/content/problem/335311/access-violation-with-mtd-and-init-seg-pragma.html
using this macro
does the calling of the desired fun before main. But. calling another fun in the same C file goes into stack overflow … whatever I try …
Any ideas?
Microsoft (R) C/C++ Optimizing Compiler Version 19.27.29112 for x64
That question is not really related to the blog post. I can’t say whether the MS compiler supports ‘redefining’? the internal section. The fact that you are running into a stack overflow suggests that this is nothing the compiler supports. The MSDN documentation explicitly states that “the name must not conflict with any standard section name”. [1] So I’d assume you are doing something unsupported here.
I suggest you post the question on a forum where it better fits (f.e. on stackoverflow or on MS the MS Q&A platform).
[1] https://docs.microsoft.com/en-us/cpp/preprocessor/section?view=vs-2019
Thank you man, it worked for me