c++windowsvisual-studiostatic-order-fiascovisual-c++-runtime

How to name sections/groups for three C++ objects when using init_seg?


I'm using init_seg to control the creation of three C++ class objects. Each object is in a different source file/translation unit. Debugging shows the objects are being created as expected during CRT initialization.

The objects are being initialized in alphabetical order of their source file. I'd like to change it because its not quite right. I visited MSDN's page on init_seg, and it states the use is:

#pragma init_seg({ compiler | lib | user | "section-name" [, func-name]} )

It appears the use of lib and section-name are mutually exclusive, so its not clear to me how to use init_seg(lib) and provide a section/group name to get the alphabetical ordering right.

When I attempt to use an alphabetized string to control order:

#pragma init_seg(lib, "01")

It results in a warning, which I am guessing means things are not going to work as expected:

warning C4081: expected ')'; found ','

When I try to insert directly into the CRT startup code directly using ".CRT$XCB", ".CRT$001", and ".CRT$XCB001" (and other variations of utilizing alphabetization):

#pragma init_seg(".CRT$XCB")

It results in another warning, which I am guessing means things are not going to work as expected:

warning C4075: initializers put in unrecognized initialization area

I found one question on Stack Overflow about it, but the answer was a guess and it does not cover multiple translation units. I also found an archive of KB104248 on the Wayback Machine, but its not much help either because it only shows the use of compiler, lib and user.

So my question is, how do I utilize init_seg to control the precise order of creation of my three objects in three different source files?


Solution

  • Here's what I found through testing on XP and VS2002/VS2003, Vista and VS2005/VS2008, Windows 7 and VS2008/VS2010, Windows 8 and VS2010/VS2012/VS2013, and Windows 10 using VS2015. #pragma_init(<name>) has been available since VC++ 1.0 days. MS does not publish too much information about it, but we know its documented from VC++1.0 (archived KB104248) through VS2017.

    1. #pragma init_seg(lib) is almost perfect. However, the object files are alphabetized in VS2008 and earlier, so the init order is a-b-c (unwanted) rather than c-b-a (desired). Its OK on VS2010 and above. What's not obvious is the order is laid out precisely as c-b-a in the vcproj files.

    2. #pragma init_seg(".CRT$XCB-0NN") seemed to work. Our std::strings STRING_A and STRING_B were created early (and the objects were in the right order), but STRING_B caused a crash at suhutdown. The address was 0x0000000d, and it appears the std::string (and its vtable) were destroyed too early.

    3. #pragma init_seg(".CRT$XCU-0NN") worked as expected during startup and shutdown. If I parsed what I read correctly, then the U in group name XCU indicates user defined objects. It means our objects were created somewhere in-between what #pragma init_seg(lib) and #pragma init_seg(user) provides.

    So here is how to initialize an object C, then an object B, then an object A from source files a.cpp, b.cpp and c.cpp.

    Source file a.cpp:

    class A
    {
        ...
    };
    
    #pragma warning(disable: 4075)
    #pragma init_seg(".CRT$XCU-030")
    A a;    // created 3rd
    #pragma warning(default: 4075)
    

    Source file b.cpp:

    class B
    {
        ...
    };
    
    #pragma warning(disable: 4075)
    #pragma init_seg(".CRT$XCU-020")
    const B b;    // created 2nd
    #pragma warning(default: 4075)
    

    Source file c.cpp:

    #pragma warning(disable: 4075)
    #pragma init_seg(".CRT$XCU-010")
    const std::string c;    // created 1st
    const std::string d;    // created 1st
    #pragma warning(default: 4075)
    

    Our use case was to create three read only objects and avoid the problems with C++'s static initialization order fiasco and Microsoft's thread local storage.

    The technique avoids missing C++ dynamic initializers in C++03. It also side steps Microsoft's inability to provide C++11's Dynamic Initialization and Destruction with Concurrency (or more correctly, Microsoft's failure to provide the core language feature for 10 years).

    Here's a reference to the issue on Thread Local Storage (TLS) on MSDN:

    On Windows operating systems before Windows Vista, __declspec( thread ) has some limitations. If a DLL declares any data or object as __declspec( thread ), it can cause a protection fault if dynamically loaded. After the DLL is loaded with LoadLibrary, it causes system failure whenever the code references the __declspec( thread ) data. Because the global variable space for a thread is allocated at run time, the size of this space is based on a calculation of the requirements of the application plus the requirements of all the DLLs that are statically linked. When you use LoadLibrary, you cannot extend this space to allow for the thread local variables declared with __declspec( thread ). Use the TLS APIs, such as TlsAlloc, in your DLL to allocate TLS if the DLL might be loaded with LoadLibrary.

    Its also worth mentioning that there does not appear to be a limit on the number of characters in the section or group name. The archived KB 104248 uses the name "user_defined_segment_name" with 26 characters.