Visual C++ 6 Unleashed

Visual C++ 6 Unleashed

By MICKEY WILLIAMS and David Bennett

Techniques for Debugging Your MFC Application

Make sure the debugger is positioned onscreen so that it doesn't overlap the program. Otherwise, the debugger might obscure the program being debugged.

From the TRACER application, select the Multiple Application Debugging option when debugging an application and one or more DLLs. This way, the name of the application that generated the error will appear in a prefix.

The Multiple Application Debugger option also is useful in tracking the order of events.

Remember that you can hard code breakpoints into the application by using the following statement:

DebugBreak();

Or, you can use this statement, which is more appropriate for MFC applications:

AfxDebugBreak();

Just be sure to remove these statements when building the Win32 release version.

AfxDump

AfxDump is a pointer to an object derived from the CObject class. You should call this function while in the debugger. Doing so dumps the state of an object while debugging. AfxDump should not be called directly by the program. The program should call the Dump member function of the appropriate object instead.

AfxDump is available in the debug version of MFC only.

The TRACER.EXE Utility

Included on the Tools menu of Developer Studio is the MFC Tracer application. From this window, you can enable or disable several sorts of trace messages, as well as disable tracing altogether. Note that trace output is available only while using the debugger.

You can set the following options with TRACER:

MFC Diagnostic Features

Many diagnostic features are included with the debug version of the MFC Library. All the features in the following list are included in all the classes derived from CObject. Remember that before you can use any of the MFC diagnostic features, you must enable diagnostic tracing by setting the afxTraceEnabled flag, and the afxTraceFlags must have a set level of detail. The simplest way to set these flags is to use the TRACER.EXE utility.

The TRACE Macro

The TRACE macro is active only in the debug version of MFC. You can build a release version of the program to deactivate all TRACE calls in the program.

The TRACE macro can handle a wide range of arguments and works similarly to printf. The following block of code demonstrates the TRACE macro:

// example for TRACE
int integer1 = 1;
char characters[] = "two";
float float1 = 3.3;

TRACE( "Integer = %d\n",integer1);
TRACE( "String = %s", characters);
TRACE( "Float = %f\n", float1);

// The Output would be:
// 'Integer = 1'
// 'String = two'
// 'Float = 3.3'

The TRACE macro is limited to sending up to 512 characters at one time. If this limit is exceeded, it causes an ASSERT.

Other TRACE Macros

Another group of TRACE macros is available. These macros are very useful when debugging Unicode, because the _T macro is not required.

Here are the additional TRACE macros:

TRACE0 Takes a format string (only) and can be used for simple text messages that are dumped to afxDump
TRACE1 Takes a format string and one argument (one variable, which is dumped to afxDump)
TRACE2 Takes a format string and two arguments (two variables, which are dumped to afxDump)
TRACE3 Takes a format string and three arguments (three variables, which are dumped to afxDump)

The ASSERT Macro

You use the ASSERT macro to check assumptions made by the functions in the program. The most common use of the ASSERT macro is to locate program errors during development.

The ASSERT macro catches errors only when using the debug version of MFC. It automatically is turned off and produces no code when the program is rebuilt with the release version of MFC.

This example shows how you can use the ASSERT clause:

int FunctionResult = AnyFunction(x);
ASSERT (FunctionResult < 0);

If the argument expression is false (0), the program is halted and the developer is alerted. Nothing happens if the argument is true (not zero).

If the argument is false, a message box appears with the following text:

assertion failed in file <name> in line <num>
Abort Retry Ignore

where <name> is the name of the source file and <num> is the line number of the assertion that failed.

Choosing Abort terminates program execution. Choosing Ignore continues the program. Choosing Retry breaks into the debugger. Neither Abort nor Ignore activates a debugger.

The ASSERT_VALID Macro

You use the ASSERT_VALID macro to test the validity of an object's internal state. ASSERT_VALID calls the AssertValid member function of the object passed as its argument.

The macro validates the pointer of the object, checks it against NULL, and calls the object's own AssertValid member functions. An alert message is displayed similar to ASSERT if any of the tests fail.

This function is available only in the debug version of MFC.

Using DEBUG_NEW to Track Memory Allocation

To assist you in keeping track of memory being allocated, the macro DEBUG_NEW is supplied with the debug version of MFC. You can use DEBUG_NEW anywhere in the code where you normally would use the new operator.

When a debug version of a program is compiled, the DEBUG_NEW macro keeps track of the filename and line number for every object it allocates. Then, when the object is dumped by DumpAllObjectsSince, each object allocated with DEBUG_NEW shows the file and line number and where it was allocated. This makes it simple to pinpoint the sources of memory leaks.

When the release version of the program is compiled, all DEBUG_NEW statements are transformed into a new operation.

First, define the macro in the source files that will have new replaced with DEBUG_NEW, as shown here:

#define new DEBUG_NEW

Now new can be used for all heap allocations. The preprocessor will substitute DEBUG_NEW when the code is compiled. In the Win32 debug version, DEBUG_NEW will create debugging information for each heap block. When the code is built with the release version, DEBUG_NEW will be replaced with a standard memory allocation (most likely, new).

Detecting Memory Leaks

Memory leaks can occur when accidentally using memory that already has been allocated, or when memory is allocated on the heap and never deallocated for reuse. Programs that run for lengths of time can really compound any of the problems mentioned earlier.

In the past, memory leaks were difficult to detect. MFC comes to the rescue by providing classes and functions you can use to detect memory leaks at the development stage. Essentially, these classes and functions take a snapshot of all memory blocks before and after a set of chosen operations. By comparing the results, it is easy to see whether all the allocated memory has been deallocated.

The set of operations can range from a single line of code to an entire program.

Share ThisShare This

Informit Network