Thursday, July 7, 2011

Writing a "Pre-Main" function - Forcing Global Initialization order within VC++

One rule of C/C++ programming that bites everyone eventually is that initialization order of global variables across compilation units is not guaranteed. I've seen programs with global variable dependencies run fine for years then suddenly develop "issues" resulting in a hard lock before main(..) is hit. I've seen other programs have dependency issues but not actually crash resulting in subtle bugs that never seem to negatively affect run-time... until they do, usually on the day before gold master when the lead programmer is in Peru.

So here's the situation, you want to create objects "a" through "g" in various files who register themselves with a manager into a list on construction... a relatively common setup and relatively simple within a single compilation module; for VC++ define them from bottom to top manager first and it will work. However across compilation modules things are not so simple and more often than not the manager will construct itself at a very inopportune time; likely after some objects have registered themselves and before all objects have.

Without simply knowing that the manager will initialize first there really isn't a "Good" solution tho there are several lazy initialization techniques.

So here's where things get "fun" and by "fun" i mean, people look at the code and get confused looks on their face.

Put the following code into a cpp file on its own and add it to your project of choice.

#pragma init_seg( ".CRT$XCB" )

class c_blog_first_class_construction
{
public:
    c_blog_first_class_construction() 
    {
        printf("first class construction\n");
    }

    ~c_blog_first_class_construction() {};
};

static c_blog_first_class_construction blog_first_class_construction;

the constructor for c_blog_first_class_construction will be called before any other constructor in your code (assuming you have nothing else like this in there).

NOTE: Expect C4075: initializers put in unrecognized initialization area, disable it if you feel the need.

the key to the code is
#pragma init_seg(...)

for more info on what this does see kb104248

The crux of it however is we are naming this compilation unit with a specific identifier, when the linker reads various .CRT groups, it combines them in one section and orders them alphabetically. This means that the user-defined global initializers (which the Visual C++ compiler puts in .CRT$XCU) will always come after CRT$XCA and before .CRT$XCZ.

So we are depending linker to do the right thing and insert our section into the CRT sorted sections (verified in VS2010 as of 7/6/2011 for x86,x64,x360). It works and its very useful.

Now if you want to get really fancy... make the constructor for c_blog_first_class_construction call a function that initializes your "Pre construction" requirements engine wide (usually memory management, assert systems).

For other platforms (GCC/SNC are the only others i use) there are other options such as "init_priority" which is a per instance attribute. These can be used to achieve the same result but on a more granular level.

It should be noted that this is not a method that one should use all over a codebase, personally i only ever use it on one compilation module and it is always VERY well commented.

enjoy.

2 comments:

Horst Kackebart said...

It's dirty and I don't need it, but I'm loving it. Very creative!

Adina said...

very informative article
pokemon crater

Visitor IP Address City