tags:

views:

361

answers:

3

I have a game with puzzles. Puzzles are created from script files using a puzzle engine ID. Puzzle engines register themselves using a static variable in a static hash. I'm having reports of crashes and we've tracked down the problem to the hash not having certain keys, which should have been added by the static functions.

Note that this is a bug we can't reproduce, that is, the method works in general. In fact I have no idea why sometimes it apparently doesn't. Is there any possibility of these static functions not being called? Maybe a compiler bug? (Visual C++ 2005)

The puzzle factory type

class PuzzleMode;
typedef PuzzleMode* (*fnPuzzleFactory)(bool* bVictory, const char* sPuzzleCode, const GFC::StringList& lParams);

Engines use this macro to make sure they get registered. There is a static bool which is initialized as the result of a function that registers the factory and returns true :

#define REGISTER_PUZZLE(CODE, CLASSNAME)                 \
 PuzzleMode* puzzleFactory##CLASSNAME (bool* bVictory, const char* sCode, const StringList& lParams)  \
 {                          \
  return new CLASSNAME(bVictory, sCode, lParams);              \
 }                          \
                           \
 static bool registerPuzzle##CLASSNAME (void)               \
 {                          \
  PuzzleMode::registerPuzzleFactory(CODE, puzzleFactory##CLASSNAME);         \
  return true;                      \
 }                          \
                           \
 static bool s_bRegisteredPuzzle##CLASSNAME = registerPuzzle##CLASSNAME();

Example, in the puzzle engine .cpp :

REGISTER_PUZZLE("bloodcollection", BloodCollection);

Somewhere else, in a .cpp :

static Hash<String, fnPuzzleFactory>* s_hPuzzleFactories = NULL;

void PuzzleMode::registerPuzzleFactory (const char* sId, fnPuzzleFactory pFactory)
{
 if (!s_hPuzzleFactories)
  s_hPuzzleFactories = new Hash<String, fnPuzzleFactory>(); 

 String sLowId = String(sId).lower();
 s_hPuzzleFactories->setAt(sLowId, pFactory);
}

All of that is supposed to happen when the app starts. Much later it's used like this

PuzzleMode* PuzzleMode::createPuzzleInstance (const char* sEngine, bool* bVictory, const GFC::StringList& lParams)
{
 // Shouldn't happen
 if (!s_hPuzzleFactories)
  s_hPuzzleFactories = new Hash<String, fnPuzzleFactory>();

 String sLowId = String(sEngine).lower();
     if (!s_hPuzzleFactories->hasKey(sLowId)) // <----- HERE
  return NULL;

 fnPuzzleFactory fnFactory = s_hPuzzleFactories->getAt(sLowId);
 return fnFactory(bVictory, sEngine, lParams);
}

And sometimes, but only sometimes, in the line marked above, the hash doesn't have the key.

Any ideas? The only thing I can think of (and I thought of it while writing this) is that the register function is called before the hash itself is initialized to NULL, but everything should crash and burn way before we find the hash doesn't have the key we're looking for.

+2  A: 

This is probably a static initialization order issue. Try rewriting it as "construct on first use".

MadKeithV
Interesting... I'll try with your suggestion.
ggambett
+1  A: 

The linker is allowed to drop a compilation unit completely if it thinks that it contains no reachable code. So if none of the functions in your .cpp file is explicitly called from elsewhere, it may get discarded entirely, and the object that should have been registered is simply missing from your executable.

Try to put a dummy external function in the .cpp of the missing puzzle and call it from your main() to see if it solves your problem.

Carl Seleborg
the standard says: "An object defined in namespace scope having initialization with side-effects must be initialized even if it is not used"
Johannes Schaub - litb
Perhaps, but that's assuming the object has not been discarded along with the rest of the compilation unit. I don't have the standard near me, but I know that GCC at least does this - this is a very common pitfall.
Carl Seleborg
then gcc is not standard compliant when it does that. we should write a bugreport then :) look at 3.7.1/2 in a draft
Johannes Schaub - litb
I'm not sure it's a bug, but whether it is or not, the fact remains that that's how several compilers behave (and it sucks!), and it's a plausible explanation.
Carl Seleborg
Static objects are only guaranteed to be initialised before the first time a function is called in the translation unit (TU). If no functions of the TU are called anywhere in the program, there is no requirement for the objects to be created.
James Hopkin
i've just deleted my messy answer since it doesn't helped the OP. but my point stands. the compiler is not allowed to eliminate the objects if their initialization causes a side effect.
Johannes Schaub - litb
Take a look at 3.6.2/3. Strictly speaking, it allows the compiler to delay initialisation of objects in an unused TU indefinitely. You can't tell the difference between that and eliminating the object creation altogether.
James Hopkin
Besides, the compiler and the linker are two different beasts. In theory, the linker knows very little about C++-specific constructs and rules.
Carl Seleborg
James, that's a good point though. But the Standard doesn't say that nowhere explicitly. it says objects with side effects must be initialized, even if they appear to be unused. But it could be delayed, that could be the problem of the OP. But some time, it *must* be initialized, can't eliminated.
Johannes Schaub - litb
so i think we talked about different things. you said it could drop the translation unit entirely, while i said it cannot, but it must be initialized. I now see that it's more intricated. The object could be initialized just at the programs termination to comply to that rule.
Johannes Schaub - litb
thanks for the fine discussion mate :)
Johannes Schaub - litb
A: 

Looks like this had nothing to do with static initialization after all - after a lot of logging we found out some unrelated code was destroying the pointer, apparently.

ggambett