"Truth never damages a cause that is just."
- Mahatma Gandhi
More pages: 1 2 3
Dealing with uninitalized memory
Friday, June 18, 2010 | Permalink

A common source of undeterministic bugs that are hard to find is uninitialized variables. In a local scope this is typically detected by the compiler, but for member variables in a class you're typically on your own. You forget to initialize a bool and in 255 cases of 256 you get 'true', so the code might appear to work if that's a reasonable initial value. Until your bool ends up on a memory address with a zero in that byte.

One way to deal with this problem is to simply initialize all memory to zero first thing in the constructor. Unfortunately, that adds a runtime cost and code size cost. I figured someone must have attacked this problem before, and I'm sure someone did, but googling on it I haven't found much about it. So I came up with my own approach, which I'll be adding to Framework4. Basically it's a small class and a set of macros to simplify stuff. In my class definition I just tag the range of variables I want to check, typically all of them.

class MyClass
    // Variables go here

This inserts a start and end tag into the class, which are basically two uint32.
In the constructor I do this:

MyClass::MyClass() : CLASS_INIT()

CLASS_INIT() will initialize all memory between the tags to 0xBAADCODE, before any other initialization happens.

At some point where I expect everything to be set up, for instance the end of the constructor or maybe after some call to Init() or whatever, I simply add CLASS_VERIFY() which will check that no memory is left as 0xBAADCODE. Also, it will check start and end tags to make sure they have not been touched, which will also detect common out of range writes.

Adding this to my simple test app I found a whole bunch of variables I didn't initialize. Most of them were simply unused though. I can't imagine what kind of bug could be avoided if something like this is added to the main system classes in a big scale project. And the best of all, this come at no extra cost at runtime because in final builds those macros are defined to nothing.



Enter the code below

Friday, June 18, 2010

fmoreira, yes, false positives are possible, although rare. Those can be special cased. I suppose a FAKE_INIT(m_MyBAADCODE_var) before the verify call should work.

Friday, June 18, 2010

Arseny Kapoulkine, working on the whole class is less flexible, plus probably not portable. The v-table is included in sizeof() and you don't want to touch that. Also, you include all of deriving classes as well, which you really don't want to overwrite.

Mike, in-place new is common in game code. Also, you have to overhead of initializing the memory.

sqrt[-1], good stuff, I'll look into those tools.

Friday, June 18, 2010

FWIW, we're using Lint and it detects those cases quite nicely.

Friday, June 18, 2010

A very good freeware tool is Cppcheck. According to the documentation, it should also check for uninitialized variables.


Arseny Kapoulkine
Friday, June 18, 2010

There is one thing about class layouts that drives me crazy. And this is changing your memory layout between builds. This leads to all sorts of weird "unreproducible" bugs.

Some compilers (gcc) allow you to declare a zero-size array member, though that's not portable.

Anyway, why do you need those markers? If that's a debugging feature, just work on the whole class.

Arseny Kapoulkine
Friday, June 18, 2010

Sorry for the accidental double-post, I hate F5 form resending

Yep, if you have virtual functions, initializing the whole class is a bad thing. However, the same can be said about members with virtual functions - i.e. you have to be careful when deciding what variables are ok to garbage-init. If you're going through this, you might as well, uh, initialize them in the ctor.

Anyway, I'm glad it works for you.

Saturday, June 19, 2010

We had a very related problem in a game I worked on; vectors weren't been properly initialized before being used (and we had a design decision to not have the default ctor init x, y, z to 0), and this could lead to both obvious and very subtle bugs.

We solved this by turning on floating point exceptions, and having a debug build ctor initialize the values to NaN, so if you tried to perform math on an uninitialized value, an exception was thrown. Kinda cool, and it helped alot.

Richard S
Saturday, June 19, 2010

If you use custom memory pools (i.e. allocate all memory at startup, partition into your own system-specific pools, and allocate from there with custom allocators), the best way I've seen is to initialize all memory to known values at startup. That way you don't have to care about per-allocation overhead of the memory fill. Depending on your needs, you can re-fill on free also.

If you go with a different fill value per user, you can often catch some of the more nasty bugs quite easily - it's really simple to base the fill value off of the username (first byte, first 4 bytes, whatever); add USERNAME=$(USERNAME) to the Additional Defines settings for the project.
This helps as a bug may be 100% reproducible on only one member of the teams build, and if looking at the memory you see their username, it makes it really clear what the problem is.

More pages: 1 2 3