clinkervisual-c++-2015

Visual C++ 2015 /GL (whole program optimization) and /OPT:REF (optimize references) prevent static initializers being called


Introduction: Global static objects in C++ are initialized before main() starts. Consider:

#include <stdio.h>
int calc_it() {
   return 1;
}
int glob = calc_it();
int main() {
   printf("glob = %d\n", glob);
   return 0;
}

The output is glob = 1, because calc_it() and the assignment are executed before main() starts. The order of the code has nothing to do with it.

Now image you have multiple source files with code like that and furthermore imagine that they somehow depend on one another (you want a certain order of execution, for whatever reason. Let's not get into if that is good or bad design.)

The order of execution is not defined by the standard, but there exist ways to impose a certain ordering on them in Visual C++. For global static objects, you can use #pragma init_seg(SECTIONNAME) in front of the object definition to specify a certain section name.

But in the end this only leads to the compiler putting (__cdecl *)(void) pointers to functions in certain linker sections (they all start with .CRT$XC). The section names are ordered lexicographically before the memory layout is determined by the linker. The default section name is .CRT$XCU. The C/C++ init code then regards the contents of these segments between .CRT$XCA and .CRT$XCZ as pointers to functions and calls them one by one.

We can do this by hand as well using the #pragma data_seg(SECTIONNAME) directive. So this:

#include <stdio.h>
void hi_there() {
   printf("hi there!\n");
}
int main() {
   printf("bye!\n");
   return 0;
}
#pragma data_seg(".CRT$XCM")
typedef void (__cdecl *atexit_func)(void);
atexit_func _init_ptr[] = { hi_there };

will output:

hi there!
bye!

How nice is that? :)

Problem description: AFAIK, since Visual C++ 2015, this will no longer work if you use the /GL option (Whole Program Optimization) together with the /OPT:REF linker option (remove unused functions and data). The reason might be that from a linker standpoint the _init_ptr is never used. In older Visual Studio version this nevertheless worked, since they never removed unused data, only usused code.

Question: How to avoid this for a single symbol only?


Solution

  • The Visual Studio linker has an option to include a certain symbol, regardless if it's referenced or not: /INCLUDE:symbol. Visual C++ provides a way to add this linker option to the compiled object file: #pragma comment(linker, "/include:symbol").

    The following code will run even if compiled with cl /O2 /GL x.cpp /link /OPT:REF (that is, for x86, 32-bit):

    #include <stdio.h>
    void hi_there() {
       printf("hi there!\n");
    }
    int main() {
       printf("bye!\n");
       return 0;
    }
    #pragma data_seg(".CRT$XCM")
    typedef void (__cdecl *atexit_func)(void);
    extern "C" atexit_func _init_ptr[] = { hi_there };
    #pragma comment(linker, "/include:__init_ptr")
    

    Note the leading extra underline after the /include: and the extern "C" to prevent name mangling.

    Update: To make the code compile for x64 as well, we need to add some extras:

    #include <stdio.h>
    void hi_there() {
       printf("hi there!\n");
    }
    int main() {
       printf("bye!\n");
       return 0;
    }
    
    #ifdef _M_X64
    #define INCLUDE_SYM(s) comment(linker, "/include:" #s)
    #else
    #define INCLUDE_SYM(s) comment(linker, "/include:_" #s)
    #endif
    
    #pragma data_seg(".CRT$XCM")
    #pragma section(".CRT$XCM", read)
    typedef void (__cdecl *atexit_func)(void);
    extern "C" atexit_func _init_ptr[] = { hi_there };
    #pragma INCLUDE_SYM(_init_ptr)