Initialize your goddamn variables, kiddies

Started by
8 comments, last by Servant of the Lord 7 years, 8 months ago
Once upon a time there was a video game.

Like many games, this one had a physics engine, and used it to reasonable effect.

Deep in the code that connected gameplay rules to the physics simulation, there was a piece of code for handling jumping.

It was a pretty tame, innocent piece of code, all in all. It set up either a controlled, normalized vector for a "launch" or a directly vertical vector for a normal jump.

For nearly two whole years, a demon lurked in this little snippet of code, waiting to come out.

One day, it spotted an opportunity. And boy did it ever seize that opportunity.

Suddenly, reports of game crashes came pouring in. By the hundreds. At its peak of devastation, that little code demon caused a random player, somewhere in the world, to crash out of the game roughly every ten minutes.

The programmers scrambled. The programmers puzzled and pondered and pored over code. And they found nothing.

For almost three entire weeks the demon ran amok, crashing innocent gamers everywhere.

In desperation, the programmers littered a private build of the game with land mines, poisoning memory and then asserting that the poisoned values never appeared at runtime.

But appear they did. In spades.

At last, the source of the mystery was uncovered. Bit by bit, the programmers whittled away at the problem, until the ultimate source of the bug was laid bare.

Once jumping was implicated in the crashes, it was relatively easy to poison the jumping physics code and wait for the assertions to fire.

From there, finding the root cause was just a matter of carefully reading a few dozen lines of code, and poof - there it was, that nasty little demon, exposed for all to see.

In one code path, the vector created by the jump logic was flawless. In another, it was partially initialized - only the Z component carried a meaningful value.

Therefore, roughly one in a billion jumps, the stack garbage that populated the X, Y, and W components of the vector would become a NaN sentinel value.

The physics engine would dutifully corrupt the state of the game, silently propagating the NaN to all kinds of arbitrary coordinates and vectors in the simulation.

And then, a few frames later, someone would query one of those corrupted vectors, and crash.

The moral, of course, is to initialize your fucking local variables, or I will eat your soul while you sleep.

The coding standard was updated, the programmers involved celebrated, and the demon was laid to rest forever.

And there was much rejoicing.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Advertisement
I REALLY like compilers that consistently detect when you're about to use a variable that hasn't been initialized. Bonus points if they still manage to compile quickly.

I've seen that same demon crush the souls of a dozen gameplay programmers.

We ended up training the CPU to yelp at the smell of the beast:

http://stackoverflow.com/questions/4454582/visual-studio-c-2008-2010-break-on-float-nan

There's also the fun cousin of that demon, the denormal numbers, hiding in the infinitesimal gap in between zero and "smallest possible float value". This demon is more insidious - it's poison only slows, instead of killing. On some CPU's these numbers work just fine as floats, except that the CPU drops into a special emulation mode that's a gazillion times slower than normal... So your physics/gameplay code continues to run, and is correct... but is now incredibly slow for some reason!

We always initialized locals pretty aggressively, choosing correctness over performance. Class members, though. Hooooly shit those were the bane of our existence for years. In later C++ versions, the ability to initialize class members in the header has single handedly changed our entire development experience.

SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
If you're looking specifically for an uninitialized structure, you could also make the constructor initialize all the fields to NaN (or some other sentinel) in debug builds, then get the CPU to break on NaNs outside that constructor. Then you could catch the use of the uninitialized structure right as it happens, instead of having to wait for it to get horribly corrupted.

If you're looking specifically for an uninitialized structure, you could also make the constructor initialize all the fields to NaN (or some other sentinel) in debug builds, then get the CPU to break on NaNs outside that constructor.


Not quite sure if this is the same thing you mean, but I vastly prefer making the default constructors for all types provide well-defined default values, and then using a "tag constructor" trick for optimized construction in places you know you want to avoid defaults and don't trust the compiler to optimize properly.

struct uninitialized_t {};
namespace { const uninitialized_t uninitialized; } // C++98/03
namespace { constexpr uninitialized_t uninitialized; } // C++11/14
constexpr inline uninitialized_t uninitialized; // C++17

template <class T, int M, int N>
struct matrix {
  T m[M][N];

  matrix() { memset(m, 0, sizeof m); }
  matrix(uninitialized_t) { /*do nothing*/ }
};

Just pay attention to code using uninitialized and review it a little more carefully; odds of bugs goes down and old-school programmer happiness goes up.

Sean Middleditch – Game Systems Engineer – Join my team!

the fun cousin of that demon, the denormal numbers, hiding in the infinitesimal gap in between zero and "smallest possible float value".

Yes, "Fun".

When dealing with numbers in games with a 1 meter scale, a good sanity test is: "Is this number less than the width of a hair or fingernail?" Any distance smaller than around 0.0001 generally ought to become 0.

If you're looking specifically for an uninitialized structure, you could also make the constructor initialize all the fields to NaN (or some other sentinel) in debug builds, then get the CPU to break on NaNs outside that constructor.


Not quite sure if this is the same thing you mean, but I vastly prefer making the default constructors for all types provide well-defined default values, and then using a "tag constructor" trick for optimized construction in places you know you want to avoid defaults and don't trust the compiler to optimize properly.


Neat, that's not what I meant but I like that better.

There's also the fun cousin of that demon, the denormal numbers, hiding in the infinitesimal gap in between zero and "smallest possible float value". This demon is more insidious - it's poison only slows, instead of killing. On some CPU's these numbers work just fine as floats, except that the CPU drops into a special emulation mode that's a gazillion times slower than normal... So your physics/gameplay code continues to run, and is correct... but is now incredibly slow for some reason!

Spent many hours debugging this issue within my audio DSP code. The code for applying an IIR filter unexplainably became about 60x slower, but only when the audio consisted of some signal followed by silence. The output values of the recursive filter would decay toward zero but never reach it. The solution is to enable flush-to-zero for the relevant code.

The coding standard was updated, the programmers involved celebrated, and the demon was laid to rest forever.


"Now, three thousand years later, the seal of the tombs are wearing thin, and unbeknownst to our young heroes, the ancient demon was buried under their own little village project."

This topic is closed to new replies.

Advertisement