Rex Jaeschke is an independent computer consultant, author and seminar leader. He participates in both ANSI and ISO C Standards meetings and is the editor of The Journal of C Language Translation, a quarterly publication aimed at implementers of C language translation tools. Readers are encouraged to submit column topics and suggestions to Rex at 2051 Swans Neck Way, Reston, VA 22091 or via UUCP at uunet!aussie!rex or aussie!rex@uunet.uu.net.
The header assert.h contains a macro that can be used for debugging purposes. This macro is called assert and it is conditionally defined based on the existence of another macro, NDEBUG. It is the programmer's responsibility to define NDEBUG; this symbol is never defined in the header or automatically by the compiler. If NDEBUG is not defined when assert.h is #included, the assert macro expands to a vacuous void expression and has no affect on the program. NDEBUG then, is key to whether or not assertions are actually inserted in the program. (Typically, your code would contain calls to assert and you would define NDEBUG on the command-line when you compile the module being debugged.) Let's look at a simple example:
#include <assert.h> #include <stdio.h> main() { int value; while (1) { printf("Enter an integer: "); scanf("%4d", &value); assert (value == 20); } }Since NDEBUG is not defined, assert expands into an assertion. With WATCOM's v7 MS-DOS compiler, the expansion is:
_assert (value == 20,"value == 20","test1.c",11);producing this:
Enter an integer: 20 Enter an integer: 19 Assertion failed: value == 20, file test1.c, line 11Abnormal Termination
The assertion causes the program to terminate abnormally but only if the argument to assert tests false and NDEBUG is not defined. In the preceding example, the first iteration of the loop produced 20 == 20 which is true so the program continued. The next loop, however, saw 19 == 20 and the _assert function called abort after writing its assertion message containing the false expression along with its source filename and line number, to stderr. Note that the actual source tokens are preserved. (In a standard-conforming implementation, this is achieved by using the stringize preprocessor operator #.) The format of the message output is implementation-defined.Standard C requires the assert argument to be an integral expression.
Some implementations define assert incorrectly. For example, using Turbo C v2, assert in this example expands to the code in Listing 1
Now while this program will, in fact, compile and work, there are some subtle problems. First, assert expands to a statement, not a void expression as required, which prevents you from using assert in constructs like:
if (...) assert(...); else ...In this case, assert expands to a statement which is then followed by the null statement, giving two statements in the true part of the if. Consequently, the compiler complains about a "dangling" else.The next problem is that fprintf has a variable argument list and needs to be called in the presence of a prototype containing an ellipsis. Borland supplies the prototype by including stdio.h in assert.h, but one standard header is not permitted to include another so that's a problem. (Borland breaks the same rule by including stdlib.h to get a prototype for abort.)
If the definition of NDEBUG is omitted, the assert macro expands to nothing in both WATCOM and Turbo C. And while this works in most cases, the macro is supposed to expand to a void expression (such as ((void)0) ). One case where the empty expansion won't work is:
exp1 ? assert(...) : ...If the call to assert expands to nothing at all, a syntax error results. However, with a void expression, it would not.While most library headers are designed to be included only once (with multiple inclusions having no extra effect), assert.h can be included multiple times such that, with the appropriate #defines and #undefs of NDEBUG, you can enable and disable assertions over various sections of code in the same source file. (This is not always supported correctly by compilers. The #undef assert is missing in Turbo C. This is necessary since you will be redefining assert with a different value.)
Since assert can cause abort to be called, it's a very heavy-handed debugging technique. Not only can you debug only one expression at a time, but when a problem is found, the program terminates abnormally, without flushing output buffers or performing logical program shutdown. For a discussion of trapping the call to abort and shutting down in a controlled fashion, see my column "exit and abort" in the June issue of The C Users Journal.
To see if your compiler's version of assert is standard-conforming, compile the program in Listing 2. No errors should result.