Section 6.14. Using the Preprocessor for Debugging


6.14. Using the Preprocessor for Debugging

In 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:

_ _FILE_ _ name of the file.

_ _LINE_ _ current line number.

_ _TIME_ _ time the file was compiled.

_ _DATE_ _ date the file was compiled.

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.

Exercises Section 6.14

Exercise 6.25:

Revise the program you wrote for the exercise in Section 6.11 (p. 214) to conditionally print information about its execution. For example, you might print each word as it is read to let you determine whether the loop correctly finds the first duplicated word that begins with an uppercase letter. Compile and run the program with debugging turned on and again with it turned off.

Exercise 6.26:

What happens in the following loop:

      string s;      while (cin >> s) {         assert(cin);         // process s      } 

Explain whether this usage seems like a good application of the assert macro.

Exercise 6.27:

Explain this loop:

      string s;      while (cin >> s && s != sought) { } // empty body      assert(cin);      // process s 




C++ Primer
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2006
Pages: 223
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net