Asserts are a valuable tool for testing code. They allow you to verify your understanding of the system you are building. A condition that must be true (an assertion) is tested to see if it is true.

Semantics

In keeping with the importance of well-defined interfaces, I'm going to separate the caller's view of an assert (its semantics) from its behavior.

The assert facility takes the form of a function call with one argument. (Whether it is actually a function, a macro, or a built-in depends on the language and the implementation). The argument is a condition (an expression) which must be true, for example:

ASSERT(pFoo != NULL);

Here is the formal definition of the ASSERT(expr) interface:

ASSERT(expr)

Asserts that an expression is true. The expression may or may not be evaluated.

  • If the expression is true, execution continues normally.
  • If the expression is false, what happens is undefined.

The false case is the important one: if the condition is false, all bets are off. There are two reasons for this. One, if the expression was never evaluated, then execution will continue even if the expression is false. Two, how a false assertion should be handled is really a question of the overall behavior of the system, not something the programmer can decide (or count on) where the ASSERT was used.

It is also important to remember that the expression may not be tested at all. It is common for asserts to be removed completely in non-debug builds, so that they add no overhead to the final product.

A note on terminology: the condition (or expression) being tested is the assertion. If the condition is tested and found to be false, that is a failed assertion, although colloquially, people often say the system "has asserted". The phrase "raise an assertion" is also used, paralleling "raise an exception".

Behavior

So what should happen if the asserted condition is false? Once the semantics are defined as they are above, the behavior can be chosen based on the system as a whole. There are a number of choices:

  • Stop abruptly. Crash the process, or set a global flag that prevents anything else from happening, or call exit(). Ironically, this is chosen when reliability it the most important concern. The reason is that once an assert is false, there's no telling what other assumptions are incorrect. There's no way to predict what the system will do. Stopping abruptly will prevent the software from doing bad things (like corrupting user data).
  • Throw an exception. If you've used exception handling throughout your application, throwing an exception could be a reasonable choice. The current activity will stop, but the overall progress of the application will be unimpeded.
  • Print a message and keep on going. This doesn't seem like a good choice, but if you want to try continuing, and don't have an exception infrastructure (why don't you?), this may be your best option.
  • Present the developer with a dialog of choices. In a debugging environment, the best choice is to defer to the developer. Put up a dialog that lets the developer choose the course of action. Windows developers are familiar with the Abort/Retry/Ignore box where Retry means Debug.

Once you've chosen a behavior for ASSERT, don't confuse the semantics with the behavior. The semantics remain the same: condition may be tested, true condition continues normally, false condition is undefined.

Traps

Of course, asserts are not without their pitfalls. For example, if the asserted expression has side-effects, you can end up with baffling bugs when the asserts are compiled away. This is bad:

ASSERT(SetImportantValue(3) == true);

Some environments (for example, Microsoft's MFC) accommodate the impulse to write these asserts by providing another macro with the same semantics as ASSERT, except that the expression is guaranteed to be evaluated:

VERIFY(SetImportantValue(3) == true);

Another trap is that each assert is one more line of code which itself could have bugs. Consider this (real) example:

ASSERT(pFoo = NULL);

Not only does the assert not do what it was supposed to do (check that pFoo is NULL), but it accidentally fixes the problem it was meant to detect. If pFoo is wrong (not NULL), this assert will set it to NULL, obscuring the problem. If the ASSERT is compiled away in a release build, the code will start working worse than it did in the debug build.

When not to use asserts

Asserts should be used only where it is "impossible" for the condition to be false. Don't use them to:

  • Test input from the user.
  • Check the validity of on-disk structures.
  • Test that your compiler is working properly (because if it isn't, the assert mechanism itself is called into question).

See also

Comments

[gravatar]
engtech 12:56 PM on 11 Jun 2006

"ASSERT(pFoo = NULL);"

The solution there is to get in the habit of always putting the constants on the left so that if you make a typo in the equation operator then it doesn't compile.

[gravatar]
Hugh Kelly 6:07 AM on 17 Nov 2006

Hi
As a non-programmer I have an asserts debug failed window coming up on boot up as follows.
m_h!=0
lsburnwatcher.exe
Can you suggest anything I can do please as O do have a problem writing to a disk.
Many thanks
Hugh

[gravatar]
Ben 6:08 AM on 24 Jan 2007

I'm a big fan of assertions (kinda like contract-based programming).

An extension that I've used that comes in handy is the static assert, which is checked at compile time:

#define STATIC_ASSERT(b) {static int _ass[b ? 1 : -1];}

(This tries to allocate an illegal array if the assertion fails.)

I find this allows me to verify some issues for portability, and allows you to do some 'risky' things (casts, pointer arithmetic).

For example:
STATIC_ASSERT(sizeof(long long) == 8);
STATIC_ASSERT(ARRAY_SIZE(people_list) == N_PEOPLE);
STATIC_ASSERT(OFFSET_OF(PT, x) == OFFSET_OF(PT_shaded, x));

The second form is useful for ensuring a constant array (for example) is properly initialized. E.g.

enum {ALICE, BOB, CHARLES, N_NAMES};
char * g_names[] = {"Alice", "Bob", "Charles"};
STATIC_ASSERT(ARRAY_SIZE(g_names) == N_NAMES);

[gravatar]
Vasudev Ram 3:11 PM on 7 Sep 2008

Good article. I like the discussion of the potential issues with asserts.

I had made my team use asserts (in C, where they're called assertions) on a project I led to develop a smallish database middleware library product, some years back. It turned out to be very effective and improved the code quality a lot. The product was then used as a key component in many large projects of the company where I then worked.

Vasudev Ram
http://www.dancingbison.com

Add a comment:

name
email
Ignore this:
not displayed and no spam.
Leave this empty:
www
not searched.
 
Name and either email or www are required.
Don't put anything here:
Leave this empty:
URLs auto-link and some tags are allowed: <a><b><i><p><br><pre>.