Assert is Just for Debugging

Remind yourself again and again that ASSERT() macros disappear in run-time mode. The ASSERT() macro is only for debugging and should always be read as, "If this is false, there is a bug in the code." This means that you must not use asserts to handle runtime error conditions, such as bad data, out-of-memory conditions and unable to open file — these should be handled by exceptions.

Conscientious programmers know it is imperative to check the return value from calls to new — it is possible to run out of memory on even the most forgiving operating system. It is a common mistake to use ASSERT() to test the return value:

Employee *pEmployee = new Employee;
ASSERT(pEmployee);   // bad use of assert
pEmployee->SomeFunction();

This is a classic programming error — every time the programmer runs the program, there is enough memory and the ASSERT() never fires. After all, the programmer is running with lots of extra RAM to speed up the compiler and debugger. The programmer then ships the executable, and the poor user, who has less memory, reaches this part of the program and the call to new fails and returns NULL. The ASSERT(), however, is no longer in the code, and there is nothing to indicate that the pointer points to NULL. As soon as the statement pEmployee ->SomeFunction(); is reached, the program crashes.

Getting NULL back from a memory assignment is not a programming error, although it is an exceptional situation, and thus should be managed with the C++ exception mechanism.

We should rewrite this bit of code to use exceptions; which are the correct mechanism for handling those areas of your code where exceptional, and undesirable circumstances may arise:

Employee *pEmployee = new Employee;
If ( ! pEmployee )   
      throw new xOutOfMemory;
pEmployee ->SomeFunction();

We'll look at exception handling in more detail a little bit later in the chapter.

Side Effects

A common but infuriating problem for programmers is the bug which only appears in the run-time code, but which runs perfectly in debug mode. If this happens to you, the first thing to suspect is the inadvertent reliance on a side-effect in your use of an ASSERT() macro.

For example, you might write:

   Employee* pEmp;
   ASSERT( PEmp = SomeFunction(); )

If you do this, you will create a particularly nasty bug. You meant to test that SomeFunction() has returned a valid, non-null pointer, but in fact, the assignment is occurring in the body of the ASSERT() itself. When you compile in non-debug mode, this macro will equate to nothing, and consequently, the call to SomeFunction() will not be made — the pointer will be left pointing to whatever happened to be in memory at the time it was allocated.

A similar problem arises when you attempt to test a value in ASSERT(), but inadvertently use the assignment operator instead:

ASSERT (x = 5);

Here, you meant to test whether x is equal to 5, but you accidentally assigned the value 5 to x instead. The test returns TRUE, because x = 5 not only sets x to 5, but returns the value 5, and because 5 is non-zero it evaluates as TRUE.

Once you pass the ASSERT() statement, x is equal to 5 (you just set it!). Your program runs fine until you turn debugging off. At that point the ASSERT() macro disappears, and x is no longer set to 5; if you had actually set x to another value prior to this line of code, then x is now left at that old value and the behavior of your program changes.

The bottom line is that ASSERT() macros should only test a value, and never assign or take any other action on which the success of your program depends.

© 1998 by Wrox Press. All rights reserved.