Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ Timesaving Techniques (2005) [eng].pdf
Скачиваний:
65
Добавлен:
16.08.2013
Размер:
8.35 Mб
Скачать

63 Creating Debugging

Macros and Classes

Technique

Save Time By

Debugging with the assert macro

Debugging with a logging class

Debugging with DBC (Design by Contract)

Testing your code

When you are debugging an application, having a set of techniques and tools that you can drop into an application will aid you in the process of finding and eliminating bugs. These techniques break

down into two general categories:

Macros used to debug techniques

Classes used in your application to debug the code itself

This technique looks at the sorts of things you can do to build up your own toolbox of debugging techniques and find the ones that work for you. As with most programming, there is no “right” or “wrong” way to debug a problem. There are techniques that work better for some people than others, and it is up to you to discover the ones you like best — and that work most efficiently. By having a library of macros that you can immediately drop into your application, you will save time developing and debugging code.

When it comes to debugging, one size definitely does not fit all. The number of kinds of techniques that people swear by is limited only by the number of programmers using them. To save time for yourself and your application development, you should pick the techniques that work for you and stick with them.

The assert Macro

The first technique we will look at is the assert macro. The assert macro is a simply defined C++ standard macro that evaluates a single argument. If that argument is true, the code continues processing. If the argument is false, the program prints out a diagnostic error message and terminates abruptly. The catch is that the assert macro is only defined when the program is not being compiled in optimized mode. An assert is turned “on” when it is checking values and printing out error messages. Asserts

388 Technique 63: Creating Debugging Macros and Classes

are turned off when they do nothing. In programming parlance, we say that the program is in “debug mode” for assert to work, and in “release mode” if asserts are turned off. Let’s look at an example of how to use the assert macro in your own code.

1. In the code editor of your choice, create a new file to hold the code for the source file of the technique.

In this example, the file is named ch63.cpp, although you can use whatever you choose. This file will contain the class definition for your automation object.

2. Type the code in Listing 63-1 into your file.

Better yet, copy the code from the source file on this book’s companion Web site.

LISTING 63-1: USING THE ASSERT MACRO

#include <assert.h> #include <string.h> #include <stdlib.h>

int func( int v1to10 )

{

assert( v1to10 >= 1 && v1to10 <= 10 ); 1 int divisor = 0;

switch ( v1to10 )

{

case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 10:

divisor = v1to10 * 2; break;

}

int retVal = 200 / divisor; return retVal;

}

int main(int argc, char **argv)

{

func(3);

func(11); return 0;

}

In the listing above, our function (func) accepts an integer value. We expect the input value to be in the range of one to ten, inclusive. Values outside of that range will cause a division-by-zero error in the function, so we want to make sure that the user doesn’t supply such a value. The assert statement (shown at the line marked 1) traps for such a condition and exits the program if the value input is outside the specified range.

3. Save the file in the source-code editor and close the editor application.

4. Compile the source-code file with your favorite compiler on your favorite operating system.

5. Run the program on the console window of your favorite operating system.

If you have done everything correctly, you should see the following output on the console window:

$ ./a.exe

assertion “v1to10 >= 1 && v1to10 <= 10” failed: file “ch8_1a.cpp”, line 7

Aborted (core dumped)

The output indicates that the assert function triggered and the program exited. The actual text you see will be dependent on your operating system, compiler, and function library, but it will look similar to this. The important aspect is that the assert macro tells you what file and line the error occurred on and the assertion statement that failed. This will provide you valuable debugging information for determining how the program failed.

Logging 389

If you compile the source code with optimization on, you will see only the core-dumped message (or your operating system’s equivalent of a divide-by-zero error) displayed.

The assert macro is useful during the initial development phase of an application, but it fails to catch run-time errors when the application is released to the user. For this reason, you should never count on assert statements to catch all possible errors.

Logging

The next logical type of debugging technique is the logging approach. Logging is the writing of data persistently in your application in order to facilitate debugging and maintenance. Much like a black box on a crashed airliner, logging records the steps the program took so you can understand how it got into the state it’s in. Unlike some other techniques, logging can be used in either development mode or user-release mode. You can even turn it on or off at run-time — and even during program execution — to see exactly what is going on. The following steps show you how to implement a logging class.

When you can turn your logging capability on or off at will, you can zero in on a problem much more quickly — which allows you to filter out extraneous program information and see only what’s pertinent to the problem. In this way, logging will save you a lot of time when you’re trying to debug a program defect.

1. In the code editor of your choice, create a new file to hold the code for the source file of the technique.

In this example, the file is named ch63a.cpp, although you can use whatever you choose. This file will contain the class definition for your automation object.

2. Type the code in Listing 63-2 into your file.

Better yet, copy the code from the source file on this book’s companion Web site.

LISTING 63-2: A LOGGING CLASS

#include <iostream> #include <string> #include <stdlib.h> #include <stdarg.h> #include <fstream>

using namespace std;

class Logger

 

 

{

 

 

 

bool

_bOn;

 

 

bool

_bForceFlush;

 

 

string

_sMessage;

 

 

string

_sFileName;

 

 

ofstream _file;

 

 

public:

 

 

 

Logger( void )

 

 

{

 

 

 

_bOn = false;

 

 

_bForceFlush = false;

 

 

}

 

 

2

Logger( const char *strFileName )

 

{

 

 

_sFileName = strFileName; _bOn = false; _bForceFlush = false;

}

Logger( const Logger& aCopy )

{

_sFileName = aCopy._sFileName; _bForceFlush = aCopy._bForceFlush; setOn( aCopy._bOn );

}

virtual ~Logger()

{

Flush(); if ( _bOn )

_file.close();

}

void setOn( bool flag )

(continued)

390 Technique 63: Creating Debugging Macros and Classes

LISTING 63-2 (continued)

{

_bOn = flag; if ( _bOn )

{

_file.open( _sFileName.c_str() );

}

}

bool getOn( void )

{

return _bOn;

}

void setForceFlush( bool flag )

{

_bForceFlush = flag;

}

bool getForceFlush( void )

{

return _bForceFlush;

}

void setFileName ( const char *strFileName )

{

_sFileName = strFileName;

}

string getFileName ( void )

{

return _sFileName;

}

 

 

 

void Log( const char *strMessage )

 

3

{

 

 

_sMessage += strMessage;

 

 

 

_sMessage += ‘\n’;

 

 

 

if ( _bForceFlush )

 

 

 

Flush();

 

 

 

}

 

 

4

void LogString( const char *fmt, ... )

 

{

 

 

char szBuffer[256];

 

 

 

va_list marker;

 

 

 

va_start( marker, fmt );

/*

 

 

Initialize variable arguments. */ vsprintf(szBuffer, fmt, marker );

_sMessage += szBuffer; _sMessage += ‘\n’;

if ( _bForceFlush ) Flush();

}

void Flush( void )

{

if ( _bOn )

_file << _sMessage << endl; _sMessage = “”;

}

};

The Logger class can be used to do a more extensive information capture within an application. You can log virtually anything you want, regardless of whether the program is compiled in a debug (development) or release (production) mode. Logging can be turned on or off, even at run-time, allowing you to configure your system whenever you want without recompiling the program. The Logger class accepts a filename in which it will write all of its output (see 2), and writes out strings using either a simple string format (see 3) or a more complicated formatted output (see 4). This makes the logging class ideal for inserting into your program and keeping track of important events for either debugging or customer support purposes.

3. Save the source file in your code editor.

Testing the Logger Class

After you create a class, you should create a test driver that not only ensures your code is correct, but also shows people how to use your code. The following steps show you how:

1. In the code editor of your choice, reopen the source file to hold the code for your test program.

In this example, I named the test program ch63a.cpp.

2. Type the code from Listing 63-3 into your file.

Better yet, copy the code from the source file on this book’s companion Web site.

Testing the Logger Class 391

LISTING 63-3: THE LOGGER CLASS TEST DRIVER

int main(int argc, char **argv)

{

Logger log(“log.txt”);

//Make the log write things out as it encounters strings,

//to avoid crashes wiping out the log.

log.setForceFlush( true );

// First, see whether the code told us to log anything. for ( int i=0; i<argc; ++i )

{

if ( !strcmp(argv[i], “-log”) )

{

log.setOn(true);

break;

}

}

log.Log(“Program Startup Arguments”); for ( int i=0; i<argc; ++i )

{

log.LogString(“Input Argument %d = %s”, i, argv[i] );

// Prompt for a string, modify it, and then write it out. while ( 1 )

{

printf(“Enter command: “); char szBuffer[ 80 ]; memset( szBuffer, 0, 80 );

if ( gets(szBuffer) == NULL ) break;

log.LogString(“Input String: %s”, szBuffer ); if ( !strlen(szBuffer) )

{

break;

}

string s = “”;

for ( int i=strlen(szBuffer)-1; i>=0; --i ) s += szBuffer[i];

log.LogString(“Output String: %s”, s.c_str() );

}

log.Log(“Finished with application\n”); return 0;

}