An Example: Assumption Verification

Verifying assumptions using assertions is an example of a common use of the preprocessor and its features. An assertion is a statement placed in source code to verify an assumption. Usually, programmers place assertions at the beginning of a function definition to verify assumptions they made when designing the function. If at run-time the assumption proves to be incorrect, the assert statement displays a notification message and stops the execution of the program. Used in this manner, assertions are an excellent tool for error detection.

All kinds of assumptions are made in programs about the data contained in variables, especially those found in parameters being passed to a function. When we design and code a function, we expect the parameters to contain valid data. If the parameters do not contain valid data, this could signify that an error exists in some other area of the program. Coding around the invalid data only serves to hide the error, whereas using an assertion can detect and point out the existence of that error.

Consider the function calculate_average that calculates the average of a series of values. We assume the caller of the function passes two non-zero integer parameters to the function. In the context of a larger program, if the second parameter were passed as zero, a run-time error would occur as a result of the divide-by-zero. How can we handle this invalid data? One way, seen in Listing 6, involves coding defensively to detect the invalid data case.

1: 2: 3: 4: 5: 6: 7: 8: 9: 10: double calculate_average(int total, int count) { // avoid divide by zero error if (count != 0) { return total / count; } else { return 0; }}
Listing 6 Defensive coding

The above version of calculate_average works in that it prevents the divide-by-zero error. It does not take into consideration that a zero count could mean that an error occurred in another part of the program. Perhaps a bug exists in the code that reads the values from the user. Or, maybe some other code erroneously overwrote the value of count. We really do not know, but using this version of calculate_average will not help us detect and locate this error.

The following version of calculate_average takes a different approach. Here, the assumption of valid data is verified using an assertion. If the caller of the function passes invalid data (that is, count equals zero) to the function, the assertion displays an error message and stops program execution. The programmer can then find the error that caused the passing of invalid data to function calculate_average.

1: 2: 3: 4: 5: 6: 7: double calculate_average(int total, int count) { // assume we are given valid data assert (count != 0); return total / count;}
Listing 7 Verifying an assumption using an assertion

The assert statement actually is a macro. Contained in library <cassert>, this macro definition is a little complex, but worth examining since it incorporates a few different uses of the preprocessor. The following example lists the definition of the assert macro from a GNU C++ compiler.

1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: /* assert.h*/ #ifdef __cplusplusextern "C" {#endif #include "_ansi.h" #undef assert #ifdef NDEBUG /* required by ANSI standard */#define assert(p) ((void)0)#else #ifdef __STDC__#define assert(e) ((e) ? (void)0 : __assert(__FILE__, __LINE__, #e))#else /* PCC */#define assert(e) ((e) ? (void)0 : __assert(__FILE__, __LINE__, "e"))#endif #endif /* NDEBUG */ void _EXFUN(__assert, (const char *, int, const char *)); #ifdef __cplusplus}#endif
Listing 8 The assert macro definition

Notice the use of conditional compilation in the definition of the assert macro. Including a definition of NDEBUG into a program would disable all the assertion checks. When releasing production versions of software, programmers typically remove assertions.