17.2 Serial Debugging

I l @ ve RuBoard

Before you start debugging, save the old, "working" copy of your program in a safe place. (If you are using a source control system such as SCCS, RCS, or PCVS, your last working version should be checked in.) Many times while you are searching for a problem, you may find it necessary to try out different solutions or to add temporary debugging code. Sometimes you will find you've been barking up the wrong tree and need to start over. That's when the last working copy becomes invaluable.

Once you have reproduced the problem, you must determine what caused it to happen. There are several methods for doing this.

17.2.1 Divide and Conquer

The divide and conquer method has already been briefly discussed in Chapter 7. It consists of putting in cout statements where you know the data is good (to make sure it really is good), where the data is bad, and several points in between. This way you can start zeroing in on the section of code that contains the error. More cout statements can further reduce the scope of the error until the bug is finally located.

17.2.2 The Confessional Method of Debugging

The confessional method of debugging is one by which the programmer explains his program to someone: an interested party, an uninterested party, a wall ”it doesn't matter to whom he explains it as long as he talks about it.

A typical confessional session goes like this:

"Hey, Bill, could you take a look at this? My program has a bug in it. The output should be 8.0 and I'm getting -8.0. The output is computed using this formula ”and I've checked out the payment value and rate and the date must be correct, unless there is something wrong with the leap-year code, which ”thank you Bill, you've found my problem."

Bill never said a word.

This type of debugging is also called a walkthrough . Getting other people involved brings a fresh point of view to the process, and frequently other people can spot problems you have overlooked.

17.2.3 Debug-Only Code

The divide-and-conquer method uses temporary cout statements. They are put in as needed and taken out after they are used. The preprocessor conditional-compilation directives can be used to put in and take out debugging code. For example:

 #ifdef DEBUG      std::cout << "Width " << width << " Height " << height << '\n'; #endif /* DEBUG */ 

The program can be compiled with DEBUG undefined for normal use, and you can define it when debugging is needed.

17.2.4 Debug Command-Line Switch

Rather than using a compile-time switch to create a special version of the program, you can permanently include the debugging code and add a special program switch that will turn on debugging output. For example:

 if (debug)       std::cout << "Width " << width << " Height " << height << '\n'; 

where debug is a variable set if -D is present on the command line when the program is run.

Using a command-line option has the advantage that only a single version of the program exists. One of the problems with "debug-only" code is that unless the code is frequently used, it can easily become stale and out of date. Frequently a programmer tries to find a bug only to discover that the debug-only code is out of date and needs fixing.

Another advantage of the debug command-line option is that the user can turn on this switch in the field, save the output, and send it to you for analysis. The runtime option should be used in all cases instead of conditional compilation, unless there is some reason you do not want the customer to be able to get at the debugging information.

Some programs use the concept of a debug level. Level 0 outputs only minimal debugging information, level 1 more information, and on up to level 9, which outputs everything.

Another variation of this debugging technique can be seen in the Ghostscript [1] program by Aladdin Enterprises. This program implements the idea of debugging letters . The command option -Z xxx sets the debugging flags for each type of diagnostic output wanted. For example, f is the code for the fill algorithm, and p is the code for the path tracer. If I wanted to trace both these sections, I would specify -Zfp .

[1] Ghostscript is a PostScript-like interpreter available from http://www.ghostscript.com.

The option is implemented by code similar to this:

 /*   * Even though we only put 1 zero, C++ will fill in the   * rest of the arrays with zeros   */  char debug[128] = {0};    // The debugging flags int main(int argc, char *argv[])  {      while ((argc > 1) && (argv[1][0] == '-')) {          switch (argv[1][1]) {              /* .... normal switch .... */              // Debug switch             case 'Z':                  debug_ptr = &argv[1][2];                  // Loop for each letter                 while (*debug_ptr != ' 
 /* * Even though we only put 1 zero, C++ will fill in the * rest of the arrays with zeros */ char debug[128] = {0}; // The debugging flags int main(int argc, char *argv[]) { while ((argc > 1) && (argv[1][0] == '-')) { switch (argv[1][1]) { /* .... normal switch .... */ // Debug switch case 'Z': debug_ptr = &argv[1][2]; // Loop for each letter while (*debug_ptr != '\0') { debug[*debug_ptr] = 1; ++debug_ptr; } break; } --argc; ++argv; } /* Rest of program */ } 
') { debug[*debug_ptr] = 1; ++debug_ptr; } break; } --argc; ++argv; } /* Rest of program */ }

Now that we've set the debug options, we can use them with code like the following:

 if (debug['p'])          std::cout << "Starting new path\n"; 

Ghostscript is a large program (some 25,000 lines) and rather difficult to debug. This form of debugging allows the user to get a great deal of information easily.

I l @ ve RuBoard


Practical C++ Programming
Practical C Programming, 3rd Edition
ISBN: 1565923065
EAN: 2147483647
Year: 2003
Pages: 364

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