Using the Debug C Run-Time Library


The first step in using the .DCRT library is to include it and turn it on so that you can start benefiting from the memory tracking as early in your project as possible. In your main precompiled header file (or whatever header file all the source files in your project will include), add the following line before any #include directive in your precompiled header file:

#define _CRTDBG_MAP_ALLOC

After the rest of your header files, include CRTDBG.H. Defining _CRTDBG_MAP_ALLOC will redirect normal calls to memory allocation and deallocation functions to special versions that record the source file and line number for each allocation or deallocation.

The second step you need to take is to turn on the DCRT library heap code. As I mentioned at the beginning of this chapter, by default, most of the features of the DCRT library are turned off. The documentation states that most of the features are turned off to keep the code small and to increase execution speed. Although size and speed are important for a release build, the whole point of a debug build is to find bugs! The increased size and reduced speed of debug builds is inconsequential. So don't hesitate to turn on all the features you think will help you. The _CrtSetDbgFlag function takes a set of flags, shown in Table 17-2, that can be OR'd together to turn on various options in the DCRT library.

Table 17-2: Debug C Run.Time Library Flags

Flag

Description

_CRTDBG_ALLOC_MEM_DF

Turn on the debug heap allocations, and use the memory block identifiers. This is the only flag that's on by default.

_CRTDBG_CHECK_ALWAYS_DF

Check and validate all memory on each allocation and deallocation request. Turning on this flag catches any underwrites and overwrites as close to when they happen as possible.

_CRTDBG_CHECK_CRT_DF

Include _CRT_BLOCK memory allocations in all leak detection and state differences. In general, unless you're having a problem with the CRT library functions, you shouldn't turn on this flag. If you do, you'll get CRT library memory allocation reports. Because the CRT library must keep some memory allocated until the true end of the program, which is after the leaked memory reporting, you'll see a large number of false positive leak reports on that memory.

_CRTDBG_DELAY_FREE_MEM_DF

Instead of truly freeing memory, keep the block allocated and in the internal heap list. The blocks are filled with the value 0xDD, so you know the memory is freed when you're looking at it in the debugger. By not freeing the memory, this flag allows you to test your program under memory stress conditions. Additionally, the DCRT library will check that you don't access the deallocated block again by ensuring that all values in the block remain 0xDD. You should always turn on this flag, but keep in mind that your program's memory requirements can easily double because the deallocated memory isn't reclaimed by the heap.

_CRTDBG_LEAK_CHECK_DF

Check for memory leaks at the end of the program. Turning on this extremely useful flag is mandatory.

After building your application with the appropriate #includes and #defines, and calling _CrtSetDbgFlag, you now have the DCRT library, with its slew of functions that help you control and report on memory usage, fully available. You can call these functions at any time. Many of them lend themselves to being used in assertions, so you can sprinkle them around freely and catch your memory problems close to the source.

The Bug in the DCRT

If you follow the instructions I just gave you in the previous section, your source code will look something like that shown in Listing 17-1. At the top of the listing, after the rest of the headers, you see the _CRTDBG_MAP_ALLOC define and the include file for CRTDBG.H. The first call inside main is to _CrtSetDbgFlag to set up the DCRT. At the bottom, I've done three allocations, a malloc and two new, and all three pieces of memory are leaked.

Listing 17-1: Setting up the DCRT and leaking memory

start example
 // This define must occur before any headers are included. #define _CRTDBG_MAP_ALLOC #include <stdio.h> #include <stdlib.h> #include <string.h> #include <tchar.h>     // Include CRTDBG.H after all other headers #include <crtdbg.h> void main ( void ) {     // Turn on the full heap checking     _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF       |                      _CRTDBG_CHECK_ALWAYS_DF    |                      _CRTDBG_DELAY_FREE_MEM_DF  |                      _CRTDBG_LEAK_CHECK_DF       ) ;         // Allocate some more memory.     TCHAR * pNew = new TCHAR[ 200 ] ;     TCHAR * pNew2 = new TCHAR[ 200 ] ;     TCHAR * pMemLeak = (TCHAR*)malloc ( 100 ) ;          _tcscpy ( pNew , _T ( "New'd memory..." ) ) ;     _tcscpy ( pNew2 , _T ( "More new'd memory..." ) ) ;     _tcscpy ( pMemLeak , _T ( "Malloc'd memory..." ) ) ;  }
end example

Since I've turned on leak checking in the code for Listing 17-1, you'll see detected memory leak output like the following in the Output window when the program executes:

Detected memory leaks! Dumping objects -> NewProblem.cpp(22) : {62} normal block at 0x002F2E58, 100 bytes long.  Data: <M a l l o c ' d > 4D 00 61 00 6C 00 6C 00 6F 00 63 00 27 00 64 00   G:\vsnet\Vc7\include\crtdbg.h(692) :   {61} normal block at 0x002F2C88, 400 bytes long.  Data: <M o r e   n e w > 4D 00 6F 00 72 00 65 00 20 00 6E 00 65 00 77 00   G:\vsnet\Vc7\include\crtdbg.h(692) :   {60} normal block at 0x002F4DC0, 400 bytes long.  Data: <N e w ' d   m e > 4E 00 65 00 77 00 27 00 64 00 20 00 6D 00 65 00  Object dump complete.

What's very nice is that the leak output format is such that double-clicking on the source line displayed will automatically jump to that line in the source file. The first leak is on line 22 of NEWPROBLEM.CPP (shown in Listing 17-1), and double-clicking jumps you to the line that did the malloc. If you double-click on the second source line listed, you jump to CRTDBG.H (line 692). While you're staring at a new call, it's certainly not the new in your source code. Since I had multiple calls to new in the program, you can see that logically all calls to new are going to show up as coming from CRTDBG.H. As you can imagine, this doesn't help much when you're looking for leaks in a huge program!

The problem lies in the declaration of new in CRTDBG.H, as shown here:

inline void* __cdecl operator new[](size_t s)         { return ::operator new[](s, _NORMAL_BLOCK, __FILE__, __LINE__); } 

If you've never experienced the joy of the placement syntax new, the preceding code could appear a little strange at first. The new operator is a very special construct in C++ in that it can take all sorts of different parameters. The call to ::operator new in the code shows that it's passing three additional parameters to new. This version of new is defined as part of CRTDBG.H. At first glance, the problem with the code isn't obvious. The __FILE__ and __LINE__ macros expand to the source name and line when compiled. However, as you've seen, they don't expand into your source and lines. The problem is with the first word in the code: inline. In release builds, inline indicates to the compiler that it's supposed to take the code inside the function and plop it directly into where it is being used instead of making a function call. However, in a debug build, inline-specified functions, as everyone should realize, don't get expanded and are treated as real functions. Hence, the __FILE__ and __LINE__ macros expand into CRTDBG.H and 692.

Since this puts a severe damper on using the DCRT, I certainly had to figure out a way to get any detected memory leaks to point to the allocation location in your source code. My original thought was to change CRTDBG.H, but because it's a system header, that didn't seem like such a great idea. After a lot of pondering, I finally came up with the following macro to be placed immediately after including CRTDBG.H in your precompiled header file. This simply turns any use of new into the placement syntax version of new, which takes the extra parameters.

#ifdef _DEBUG #ifndef NEW_INLINE_WORKAROUND #define NEW_INLINE_WORKAROUND new ( _NORMAL_BLOCK ,\                                     __FILE__ , __LINE__ ) #define new NEW_INLINE_WORKAROUND #endif #endif  // _DEBUG

Sharp-eyed readers might immediately think there's a problem with my NEW_INLINE_WORKAROUND trick. The one big problem with my approach is that if you have a class that defines the new operator, my macro will cause problems because your declarations will be all messed up. Although you might not define new operators for your classes, libraries such as MFC and STL certainly do. The whole trick is to define this only inside your precompiled header file and to include only libraries like MFC and STL inside your precompiled header as well as taking advantage of the improved #pragma push_macro/#pragma pop_macro directives. To see exactly what to do, read the Common Debugging Question appearing later in this section, titled "What are the rules for properly precompiling headers?"

If you do have your own classes or use classes that define the new operator, you can still use the NEW_INLINE_WORKAROUND macro if you're willing to add some code and a few directives to your class. The following code shows a stripped-down class with the necessary parts in it to make NEW_INLINE_WORKAROUND work like a champ.

// Save off the definition of new on the macro stack. #pragma push_macro ( "new" ) // Undefine new so I don't blow the class declaration. #ifdef new #undef new #endif     class TestClass { public :     // The placement syntax version of new with the proper prototype needed // to ensure the source and line information is recorded for the // allocation. #ifdef _DEBUG     // iSize        - The size of the allocation.     // iBlockType   - The DCRT block type.     // lpszFileName - The source file name.     // nLine        - The source line.     static void * operator new ( size_t nSize        ,                                  int    iBlockType   ,                                  char * lpszFileName ,                                  int    nLine         )     {         // Put any special code in here you need.                  // Allocate the actual memory with _malloc_dbg and pass in all         // the parameters to record the allocation location.         return ( _malloc_dbg ( nSize         ,                                iBlockType    ,                                lpszFileName  ,                                nLine          ) ) ;     } #endif  // _DEBUG } ;     // Restore the new macro to the saved value (i.e., the  // NEW_INLINE_WORKAROUND). #pragma pop_macro ( "new" ) 

The big trick that allows this to work is the new #pragma push_macro and #pragma pop_macro directives. These save off an existing macro definition to an internal compiler stack and restore the previous value, respectively. You'll have to include the pragma directives around each class that has the new operator overloaded since pragmas cannot be macroized. The extra new operator you'll have to include is the one that the NEW_INLINE_WORKAROUND macros call to affect the actual allocation. Even though the extra new operator is a little inconvenient, at least you're going to get all your leaks completely reported. If you want to see how everything works, check out the FixedNewProblem project included with this book's sample files.

start sidebar
Common Debugging Question: What are the rules for properly precompiling headers?

As I mentioned when discussing how to work around the bug in the DCRT so that you can properly get leaks from memory allocated with the new operator and display the correct source line, getting your precompiled headers straight is critical. Not only will this make your memory debugging easier, it will speed up your compiles greatly. The precompiled header file is basically the parse tree for those files specified in the .H file (traditionally named STDAFX.H), written to disk. Hence, you're compiling it only once instead of each time you build a .C/.CPP file.

Here are the rules for what to put in your precompiled header file:

  1. All CRT/compiler supplied library included headers.

  2. If you're using brackets around the name in the include statement, the header files you develop should be in quotes.

  3. Any third-party library header files, such as BUGSLAYERUTIL.DLLs.

  4. Any headers from your project that haven't changed in more than a month or two.

end sidebar

Useful DCRT Functions

One of the most useful DCRT library functions is _CrtCheckMemory. This function walks through all the memory you've allocated and checks to see whether you have any underwrites or overwrites and whether you've used any previously freed blocks. This one function alone makes the entire DCRT library worth using. A great technique for tracking down memory problems is to scatter ASSERT ( _CrtCheckMemory ( ) ) ; calls throughout your code. That way you'll catch those underwrites and overwrites as close to where they occur as possible.

Another set of functions allows you to easily check the validity of any piece of memory. The _CrtIsValidHeapPointer, _CrtIsMemoryBlock, and _CrtIsValidPointer functions are perfect for use as debugging parameter validation functions. These functions, combined with _CrtCheckMemory, offer excellent memory checking features.

Another neat feature of the DCRT library is the memory state functions: _CrtMemCheckpoint, _CrtMemDifference, and _CrtMemDumpStatistics. These functions make it easy to do before-and-after comparisons of the heap to see whether anything is amiss. For example, if you're using a common library in a team environment, you can take before-and-after snapshots of the heap when you call the library to see whether there are any leaks or to see how much memory the operation uses.

The icing on the memory-checking cake is that the DCRT library allows you to hook into the memory allocation code stream so that you can see each allocation and deallocation function call. If the allocation hook returns TRUE, the allocation is allowed to continue. If the allocation hook returns FALSE, the allocation fails. When I first discovered this functionality, my immediate thought was that, with a small amount of work, I could have a means to test code in some really nasty boundary conditions that would otherwise be very difficult to duplicate. You can see the result of this brainstorm in MemStress, a feature of BUGSLAYERUTIL.DLL that allows you to force allocation failures in your programs, which I'll present later in the chapter.

The cherry on top of the icing on the memory-checking cake is that the DCRT library also allows you to hook the memory dump functions and to enumerate client blocks (your allocated memory). You can replace the default memory dump functions with custom dump functions that know all about your data. Now, instead of seeing the cryptic dumped memory you get as the default (which besides being hard to decipher isn't that helpful), you can see exactly what the memory block contains and format it exactly as you want. MFC has the Dump function for this purpose, but it works only with CObject-derived classes. If you're like me, you don't spend your entire coding life in MFC and you need dumping functions that are more generic to accommodate different types of code.

The client block enumeration feature, as the name implies, allows you to enumerate the memory blocks you've allocated. This excellent feature gives you the power to create some interesting utilities. For example, in the MemDumperValidator functions in BUGSLAYERUTIL.DLL, I call the dumping hooks from the client block enumerator so that the enumeration can dump and validate many types of allocated memory in one operation. This extensible validation is critical in that it allows you to do deep validations instead of the surface checks of underwrites and overwrites. By deep validation, I mean an algorithm that knows the data formats in the memory block and walks those formats making sure that each data element is correct.




Debugging Applications for Microsoft. NET and Microsoft Windows
Debugging Applications for MicrosoftВ® .NET and Microsoft WindowsВ® (Pro-Developer)
ISBN: 0735615365
EAN: 2147483647
Year: 2003
Pages: 177
Authors: John Robbins

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