Jacques Mattheij

Technology, Coding and Business

a C Heisenbug in the wild

Just today while reading some code I came upon the following little gem:

assert(someinitialization() == FALSE);

In code that has been in production for a long time but that exhibited subtle bugs, which mysteriously vanished whenever someone tried to debug it, so they left the debugging on (with a considerable performance penalty) just to avoid having to fix the problem. I figured I’d take a crack at the issue and started to simply read the code looking for anything out of the ordinary having to do with debugging.

If you’re an old hand at C programming you will likely have your alarm bells go off immediately, but if you aren’t it may take you a few moments to spot the bug in that line.

It is interesting that even though compilers and pre-processors give you all kinds of help in writing code and optimizing it they still give you more than enough rope to hang yourself with and double barreled, down pointing shotguns for foot shooting while trying to follow best practices.

The standard library functions are stable enough that I think that the above error could have been flagged with an appropriate warning.

Assertions are supposed to make your life easier by making sure that things that should hold true indeed do so, not harder!

Just like any tool, of course they have a sharp, business end and if you point that end towards yourself you’ll get hurt but the subtlety with which that can happen is probably enough to bite many a novice.

If you can’t spot the mistake, try the following code segment, with and without debugging:

#include <stdio.h>
#include <assert.h>

#define FALSE 0

char * p = (char)5; / contrived bad pointer */

int someinitialization()

{ p = “abc”;

return FALSE;

}

int main(int argc, char ** argv)

{ assert(someinitialization() == FALSE);

printf("%s\n",p);

}

Compile firstlike this:

cc -o bug bug.c

and run with

./bug

Then compile like this:

cc -DNDEBUG -o bug bug.c

And run again.

This particular class of bugs is called Heisenbugs, because the observer (the person trying to debug the problem) seems to mysteriously change the workings of the program just by using their sheer powers of observation. Of course that isn’t the whole story but it definitely can look like that. In this case you actually have to recompile the program but there are alternatives where an executable running under the debugger produces different results than when loaded straight from the command line. You can read about this one and some other interesting specimen here: http://en.wikipedia.org/wiki/Unusual_software_bug

This particular behavior of ‘assert’ is desirable is for performance reasons, by simply adding the -DNDEBUG flag you are telling the compiler that you trust your code to work without the training wheels provided by assert and that you want to strip all of them out of the code. Of course any side effects caused by the code inside the assertions suffer the same fate.

So, assert early, often and side effect free.