Inapoi
Inainte
Cuprins
Debugging hints
In an ideal environment, you have an
excellent debugger available that easily makes the behavior of your program
transparent so you can quickly discover errors. However, most debuggers have
blind spots, and these will require you to embed code snippets in your program
to help you understand what’s going on. In addition, you may be developing
in an environment (such as an embedded system, which is where I spent my
formative years) that has no debugger available, and perhaps very limited
feedback (i.e. a one-line LED display). In these cases you become creative in
the ways you discover and display information about the execution of your
program. This section suggests some techniques for doing
this.
Debugging flags
If you hard-wire debugging code into a
program, you can run into problems. You start to get too much information, which
makes the bugs difficult to isolate. When you think you’ve found the bug
you start tearing out debugging code, only to find you need to put it back in
again. You can solve these problems with two types of flags: preprocessor
debugging flags and runtime debugging flags.
Preprocessor debugging flags
By using the preprocessor to
#define one or more debugging flags (preferably in
a header file), you can test a flag using an
#ifdef statement and conditionally include
debugging code. When you think your debugging is finished, you can simply
#undef the flag(s) and the code will automatically
be removed (and you’ll reduce the size and runtime overhead of your
executable file).
It is best to decide on names for
debugging flags before you begin building your project so the names will be
consistent. Preprocessor flags are traditionally distinguished from variables by
writing them in all upper case. A common flag name is simply DEBUG (but
be careful you don’t use NDEBUG, which is reserved in C). The
sequence of statements might be:
#define DEBUG // Probably in a header file
//...
#ifdef DEBUG // Check to see if flag is defined
/* debugging code here */
#endif // DEBUG
Most C and C++ implementations will also
let you #define and #undef flags from the compiler command line,
so you can re-compile code and insert debugging information with a single
command (preferably via the makefile, a tool that will be described shortly).
Check your local documentation for details.
Runtime debugging flags
In some situations it is more convenient
to turn debugging flags on and off during program execution, especially by
setting them when the program starts up using the command line. Large programs
are tedious to recompile just to insert debugging code.
To turn debugging code on and off
dynamically, create bool flags:
//: C03:DynamicDebugFlags.cpp
#include <iostream>
#include <string>
using namespace std;
// Debug flags aren't necessarily global:
bool debug = false;
int main(int argc, char* argv[]) {
for(int i = 0; i < argc; i++)
if(string(argv[i]) == "--debug=on")
debug = true;
bool go = true;
while(go) {
if(debug) {
// Debugging code here
cout << "Debugger is now on!" << endl;
} else {
cout << "Debugger is now off." << endl;
}
cout << "Turn debugger [on/off/quit]: ";
string reply;
cin >> reply;
if(reply == "on") debug = true; // Turn it on
if(reply == "off") debug = false; // Off
if(reply == "quit") break; // Out of 'while'
}
} ///:~
This program continues to allow you to
turn the debugging flag on and off until you type “quit” to tell it
you want to exit. Notice it requires that full words are typed in, not just
letters (you can shorten it to letter if you wish). Also, a command-line
argument can optionally be used to turn debugging on at startup – this
argument can appear anyplace in the command line, since the startup code in
main( ) looks at all the arguments. The testing is quite simple
because of the expression:
string(argv[i])
This takes the argv[i] character
array and creates a string, which then can be easily compared to the
right-hand side of the ==. The program above searches for the entire
string --debug=on. You can also look for --debug= and then see
what’s after that, to provide more options. Volume 2 (available from
www.BruceEckel.com) devotes a chapter to the Standard C++ string
class.
Although a debugging flag is one of the
relatively few areas where it makes a lot of sense to use a global variable,
there’s nothing that says it must be that way. Notice that the variable is
in lower case letters to remind the reader it isn’t a preprocessor
flag.
Turning variables and expressions into
strings
When writing debugging code, it is
tedious to write print expressions consisting of a character array containing
the variable name, followed by the variable. Fortunately, Standard C includes
the
stringize
operator ‘#’, which was used earlier in this chapter. When
you put a # before an argument in a preprocessor macro, the preprocessor
turns that argument into a character array. This, combined with the fact that
character arrays with no intervening punctuation are concatenated into a single
character array, allows you to make a very convenient macro for printing the
values of variables during debugging:
#define PR(x) cout << #x " = " << x << "\n";
If you print the variable a by
calling the macro PR(a), it will have the same effect as the
code:
cout << "a = " << a << "\n";
This same process works with entire
expressions. The following program uses a macro to create a shorthand that
prints the stringized expression and then evaluates the expression and prints
the result:
//: C03:StringizingExpressions.cpp
#include <iostream>
using namespace std;
#define P(A) cout << #A << ": " << (A) << endl;
int main() {
int a = 1, b = 2, c = 3;
P(a); P(b); P(c);
P(a + b);
P((c - a)/b);
} ///:~
You can see how a technique like this can
quickly become indispensable, especially if you have no debugger (or must use
multiple development environments). You can also insert an #ifdef to
cause P(A) to be defined as “nothing” when you want to strip
out
debugging.
The C assert( ) macro
In the standard header file
<cassert>
you’ll find assert( ), which is a convenient debugging
macro. When you use assert( ), you give it an argument that is an
expression you are “asserting to be true.” The preprocessor
generates code that will test the assertion. If the assertion isn’t true,
the program will stop after issuing an error message telling you what the
assertion was and that it failed. Here’s a trivial
example:
//: C03:Assert.cpp
// Use of the assert() debugging macro
#include <cassert> // Contains the macro
using namespace std;
int main() {
int i = 100;
assert(i != 100); // Fails
} ///:~
The macro originated in Standard C, so
it’s also available in the header file assert.h.
When you are finished debugging, you can
remove the code generated by the macro by placing the line:
#define NDEBUG
in the program
before the inclusion of <cassert>, or by defining NDEBUG on the
compiler command line. NDEBUG is a flag used in <cassert> to change
the way code is generated by the macros.
Later in this book, you’ll see some
more sophisticated alternatives to assert( ).
 |
|