Lucas
Lucas

Reputation: 641

Problems with Static Initialization

I'm having some weird issues with static initalization. I'm using a code generator to generate structs and serialization code for a message passing system I wrote. In order to have a way of easily allocating a message based on it's message id I have my code generator ouput something similar to the following for each message type:

MessageAllocator s_InputPushUserControllerMessageAlloc(INPUT_PUSH_USER_CONTROLLER_MESSAGE_ID, (AllocateMessageFunc)Create_InputPushUserControllerMessage);

The MessageAllocator class basically looks like this:

MessageAllocator::MessageAllocator( uint32_t messageTypeID, AllocateMessageFunc func )
{
    if (!s_map) s_map = new std::map<uint32_t, AllocateMessageFunc>();
    if (s_map->insert(std::make_pair(messageTypeID, func)).second == false)
    {
        //duplicate key!
        ASSERT(false, L"Nooooo!");
    }
    s_count++;
}

MessageAllocator::~MessageAllocator()
{
    s_count--;
    if (s_count == 0) delete s_map;
}

where s_map and s_count are static members of MessageAllocator. This works most of the time but sometimes messages are not added to the map. For example, this particular message is not added unless i call Create_InputPushUserControllerMessage() somewhere in my startup code, however other messages work fine. I thought this might be something to do with the linker incorrectly thinking the type is unreferenced and removing it so I disabled that using the /OPT:NOREF switch (I'm using Visual Studio 2008 SP1) but that had no effect.

I'm aware of the problem of the "static initialization order fiasco" but as far as I know the order in which these objects are created shouldn't alter the result so this seems ok to me.

Any insight here would be appreciated.

Upvotes: 0

Views: 1202

Answers (4)

Lucas
Lucas

Reputation: 641

Turns out that the object files containing the static initializers were not included by the linker because nothing referenced any functions in them. To work around this I extern "C"-ed one of the generated functions so that it would have a predictable non-mangled name and then forced a reference to it using a pragma like this for each message

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

which I put in the generated header file that is later included in all the other non-generated files. It's MSVC only and kind of hack but I assume I can do something similar on GCC once I eventually port it.

Upvotes: 0

Johannes Schaub - litb
Johannes Schaub - litb

Reputation: 507065

Put the static into a class so it is a static member of a class

struct InputPushUserControllerMessageAlloc { static MessageAllocator s_obj; };

MessageAllocator InputPushUserControllerMessageAlloc::s_obj(
    INPUT_PUSH_USER_CONTROLLER_MESSAGE_ID, 
    (AllocateMessageFunc)Create_InputPushUserControllerMessage); 

The Standard allows it to delay initialization of objects having namespace scope until any function/object from its translation unit is used. If the initialization has side-effect, it can't be optimized out. But that doesn't forbid delaying it.

Not so of objects having class-scope. So that might forbid it optimizing something there.

Upvotes: 2

martsbradley
martsbradley

Reputation: 158

You are not setting the pointer back to null.

MessageAllocator::~MessageAllocator()
{
    s_count--;
    if (s_count == 0) 
    {
          delete s_map;
          s_map = 0;
    }
}

Upvotes: 0

Loki Astari
Loki Astari

Reputation: 264491

I would change s_map from a static class member into a static method member:

std::map<uint32_t,AllocateMessageFunc>& MessageAllocator::getMap()
{
    // Initialized on first use and destroyed correctly on program termination.
    static std::map<uint32_t,AllocateMessageFunc>   s_map;

    return s_map;
}
MessageAllocator::MessageAllocator( uint32_t messageTypeID, AllocateMessageFunc func )
{
    if (getMap().insert(std::make_pair(messageTypeID, func)).second == false)
    {
         //duplicate key!
         ASSERT(false, L"Nooooo!");
    }
}

No need for destructor or a count.

If your global objects are in separate DLL's(or shared libs) that are lazy loaded. This may cause a problem similar to your description.

Upvotes: 1

Related Questions