Asserts

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:

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:

See also

Comments

[gravatar]
"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]
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]
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]
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:

Ignore this:
Leave this empty:
Name is required. Either email or web are required. Email won't be displayed and I won't spam you. Your web site won't be indexed by search engines.
Don't put anything here:
Leave this empty:
Comment text is Markdown.