6.14. Using the Preprocessor for DebuggingIn Section 2.9.2 (p. 71) we learned how to use preprocessor variables to prevent header files being included more than once. C++ programmers sometimes use a technique similar to header guards to conditionally execute debugging code. The idea is that the program will contain debugging code that is executed only while the program is being developed. When the application is completed and ready to ship, the debugging code is turned off. We can write conditional debugging code using the NDEBUG preprocessor variable: int main() { #ifndef NDEBUG cerr << "starting main" << endl; #endif // ... If NDEBUG is not defined, then the program writes the message to cerr. If NDEBUG is defined, then the program executes without ever passing through the code between the #ifndef and the #endif. By default, NDEBUG is not defined, meaning that by default, the code inside the #ifndef and #endif is processed. When the program is being developed, we leave NDEBUG undefined so that the debugging statements are executed. When the program is built for delivery to customers, these debugging statements can be (effectively) removed by defining the NDEBUG preprocessor variable. Most compilers provide a command line option that defines NDEBUG: $ CC -DNDEBUG main.C has the same effect as writing #define NDEBUG at the beginning of main.C. The preprocessor defines four other constants that can be useful in debugging:
We might use these constants to report additional information in error messages: if (word.size() < threshold) cerr << "Error: " << _ _FILE_ _ << " : line " << _ _LINE_ _ << endl << " Compiled on " << _ _DATE_ _ << " at " << _ _TIME_ _ << endl << " Word read was " << word << ": Length too short" << endl; If we give this program a string that is shorter than the tHReshold, then the following error message will be generated: Error: wdebug.cc : line 21 Compiled on Jan 12 2005 at 19:44:40 Word read was "foo": Length too short Another common debugging technique uses the NDEBUG preprocessor variable and the assert preprocessor macro. The assert macro is defined in the cassert header, which we must include in any file that uses assert. A preprocessor macro acts something like a function call. The assert macro takes a single expression, which it uses as a condition:
assert(expr)
As long as NDEBUG is not defined, the assert macro evaluates the condtion and if the result is false, then assert writes a message and terminates the program. If the expression has a nonzero (e.g., true) value, then assert does nothing. Unlike exceptions, which deal with errors that a program expects might happen in production, programmers use assert to test conditions that "cannot happen." For example, a program that does some manipulation of input text might know that all words it is given are always longer than a threshold. That program might contain a statement such as: assert(word.size() > threshold); During testing the assert has the effect of verifying that the data are always of the expected size. Once development and test are complete, the program is built and NDEBUG is defined. In production code, assert does nothing, so there is no run-time cost. Of course, there is also no run-time check. assert should be used only to verify things that truly should not be possible. It can be useful as an aid in getting a program debugged but should not be used to substitute for run-time logic checks or error checking that the program should be doing.
|