C++ global variable not initialized when linked through static libraries, but OK when compiled with source

t.g. picture t.g. · Feb 27, 2012 · Viewed 14.9k times · Source

I have created a system that automatically registers function objects (functors) into a map based on the constructor of an global instance.

In each cpp file that defines the functor, there's a global instance of the registrar class instance to register the functor to a singleton std::map<int, std::function<...> > object.

This is the definition of registrar class:

template
<
    typename map_type,
    typename handler_type
>
struct registrar
{
    registrar
        (
             map_type& map_object,
             boost::uint16_t cmd_code,
             const handler_type& handler
        )
        {
          map_object.insert(std::pair<boost::uint16_t, handler_type>(cmd_code, handler));
        }
};

in each .cpp file. The global instance is defined like this:

namespace one_way
{
    static registrar <in_out_map_type, handler>
        post_receiver(in_out_map_type::instance(), command, handlers());
}

All works fine if I compile all the cpp with the main.cpp together. But If I compile the cpp file into a static library and link it to main.cpp, the registration does not work.

I tested with VC10 and GCC4.61 both on Windows & and Ubuntu 11.10. Both fail.

I found a thread with the same problem but OP did not say whether he solved it or not.

Am I missing anything?


Edit


Thanks for all responses including the comments.

Every response indeed helped me to think more and investigate deep into this method. After all the study and trials, I finally gave up the idea of relying on global/static variable for self-registration across binary boundaries, because there's no portable way to guarantee it will work.

My final way is to keep the registration within one binary.

Answer

Andrew Green picture Andrew Green · Jul 5, 2012

Short answer for android NDK work, any static libs that are affected by this problem should be added to the LOCAL_WHOLE_STATIC_LIBRARIES variable -- they'll then be referenced using the -Wl,--whole-archive flag and won't be subject to stripping.

Longer answer for MSVC:

Static variables in a translation unit are initialized before any regular code in the translation unit executes. In practice the initialization happens when the containing executable or dynamic library is loaded. When your \c main() is called, or your call to LoadLibrary()/dlopen() completes, any static variables will have been initialized.

The Problem, as described by MSDN:

Constructors and assignment by global function or static methods in the declaration do not create a reference and will not prevent /OPT:REF elimination. Side effects from such code should not be depended on when no other references to the data exist.

It can be convenient to place the object code from multiple translation units in a single file, a static library conventionally named with a \c .lib or \c .a suffix. The MSVC linker does dependency analysis on static libraries and will not include code that is not referenced by the including entity.

The common pattern of using a static variable to declare and cause the registration of a factory object can fail in this circumstance -- the MSVC linker deems the static as being unreachable and strips it from the result.

Solutions

A useful google search: http://www.google.com/search?q=msvc+factory+static+library

One solution is to set the /OPT:NOREF linker flag on the including entity. However, this is an all or nothing setting, and will require that all included libraries be fully linkable.

If something in the file containing the static is referenced (directly or indirectly) by the including entity, then by the language rules the static itself must be preserved.

The most basic approach is to put a dummy function in the file, and reference that from somewhere known to be considered reachable.

Another approach is to use the /INCLUDE linker flag to reference an entity in the problem file. Assuming an entity named DummyForLinkProblem, this can be done in the including entity's source:

#pragma comment(linker, "/include:DummyForLinkProblem")

ZooLib's Solution

ZooLib entities currently affected by this problem are those in ZFile_Win.cpp, ZGRgnRep_HRGN.cpp, ZNet_Internet_WinSock.cpp, ZStreamRWCon_SSL_Win.cpp, ZTextCoder_Win.cpp and ZUnicode_Normalize_Win.cpp.

We #include ZCompat_MSVCStaticLib.h in the corresponding header files, and put in each a ZMACRO_MSVCStaticLib_Reference(ModifiedFileName). In the cpp files we put a ZMACRO_MSVCStaticLib_cpp(ModifiedFileName). The ModifiedFileName is generally the filename with the leading Z and file extension removed, the same style as used in ZCONFIG_API_XXX macros.

To ensure that your executable or library does not strip these entities, simply #include the appropriate header file from known referenced code in your including entity. This will cause a non-executing reference to occur, and things will work as expected.