The CRT Debug Heap

Introduction

Microsoft Visual C++ provides a heap implementation called the CRT Debug Heap or Debug Heap. This heap implementation provides features to help developers debug their code but at a performance cost. This tutorial explains how to use the CRT Debug Heap to check that a program doesn't leak any memory.

A complete description of the CRT Debug Heap and API can be found in the MSDN Library [1]. We will introduce the APIs relevant for memory leak detection but we refer the reader to the MSDN library for a complete description of the APIs.

Using the CRT Debug Heap instead of the normal heap

Typical Visual C++ projects have two configurations: a Debug configuration and a Release configuration. The CRT Debug Heap is the default heap for the Debug configuration. The normal heap is used by the Release configuration. These settings can be changed via the project properties. In Visual C+ .NET 2003 the setting is called Runtime Library and is located on the C/C++ - Code Generation tab opened via the Project - Properties menu as shown in Figure 1.

Screenshot of the Runtime Library selection
Figure 1: The Runtime Library settings

Detecting memory leaks

The basic method for detecting memory leaks in some piece of code is to take snapshots of the heap before and after this piece of code and compare them. The following table shows the functions and structures are used to take a snapshot of the heap state and display it. Only a brief description of the functions is provided. For more details see the MSDN Library [1].

Function/structureDescription
_CrtSetReportModeWhen using _CrtMemDumpStatistics we need to speficy the output method for the report. This function is used for this purpose together with _CrtSetReportFile.
_CrtSetReportFileWhen the report mode specified in the _CrtSetReportMode function is _CRTDBG_MODE_FILE, this function is used to specify the destination file. This is also used to specify that the report should go to the standard output.
_CrtMemStateThis structure contains information about the state of the heap. It is initialized by the _CrtMemCheckpoint function and can be displayed by the _CrtMemDumpStatistics function or you can access the contents of the structure directly and provide your own reporting function.
_CrtMemCheckpointThis function is used to store the current state of the heap in a _CrtMemState structure.
_CrtMemDumpStatisticsThis function displays the contents of a _CrtMemState structure. Use the _CrtSetReportMode and _CrtSetReportFile functions to decide where the report should be written.
_CrtSetDbgFlagThis function allows the application to control how the debug heap manager tracks memory allocations.

To use these functions include <crtdbg.h> in your source files.

Simple leak detection example

This example shows what happens when an object is allocated on the heap. This example can be downloaded from the Downloads page. Here is a quick summary of what the code below does.

  1. Change the report mode so that all statistics are sent to the standard output using _CrtSetReportMode and _CrtSetReportFile.
  2. Dump the initial state of the heap.
  3. Allocate an int on the heap.
  4. Dump the state of the heap after the int has been allocated.
  5. Delete the int.
  6. Dump the state of the heap before we exit.

#include "Main.h"
#include <stdio.h>
#include <crtdbg.h> // Header file for the debug heap API

int main(int argc, char** argv)
{
   // Send all reports to STDOUT
   _CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE );
   _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDOUT );
   _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE );
   _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDOUT );
   _CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_FILE );
   _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDOUT );

   // Get the initial state of the heap and display it
   printf("Initial heap state:\r\n");
   DisplayHeapState();

   // Allocate an integer on the heap
   int* dummy = new int;

   // Get the state of the heap when the integer has been allocated and display it
   printf("\r\nHeap with int allocated state:\r\n");
   DisplayHeapState();

   // Delete the integer from the heap
   delete dummy;

   // Get the final state of the heap and display it
   printf("\r\nFinal heap state:\r\n");
   DisplayHeapState();

   return 0;
}

void DisplayHeapState()
{
   _CrtMemState state;
   _CrtMemCheckpoint(&state);
   _CrtMemDumpStatistics(&state);
}

Here is the output generated by the code above. For the purpose of this example we only consider the blocks called "Normal Blocks". We will explain what the other block types are and what they are used for later.

I have highlighted the interesting bits in blue and orange. The orange result clearly shows that we have 1 block of 4 bytes allocated on the heap after we allocate the int. An int is indeed usually 4 bytes although this may depend on the platform. After we delete the int this block disappears. This provides a very convenient way of checking for memory leaks by verifying that the number of blocks allocated on the heap when we exit our program is the same as at the start of the program. The number of blocks allocated on the heap at the start may not be null due to global variables for instance as shown in the next example.

Initial heap state:
0 bytes in 0 Free Blocks.
0 bytes in 0 Normal Blocks.
11435 bytes in 58 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 11435 bytes.
Total allocations: 16165 bytes.

Heap with int allocated state:
0 bytes in 0 Free Blocks.
4 bytes in 1 Normal Blocks.
11435 bytes in 58 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 11439 bytes.
Total allocations: 16169 bytes.

Final heap state:
0 bytes in 0 Free Blocks.
0 bytes in 0 Normal Blocks.
11435 bytes in 58 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 11439 bytes.
Total allocations: 16169 bytes.
Press any key to continue

Global variable example

This example shows what happens when a global variable is present. This example can be downloaded from the Downloads page.

#include "Main.h"
#include <stdio.h>
#include <crtdbg.h> // Header file for the debug heap API

int* dummy = new int;

int main(int argc, char** argv)
{
   // Send all reports to STDOUT
   _CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE );
   _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDOUT );
   _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE );
   _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDOUT );
   _CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_FILE );
   _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDOUT );

   // Get the initial state of the heap and display it
   printf("Initial heap state:\r\n");
   DisplayHeapState();

   return 0;
}

void DisplayHeapState()
{
   _CrtMemState state;
   _CrtMemCheckpoint(&state);
   _CrtMemDumpStatistics(&state);
}

The output of this program is shown below. It confirms that global variables are initialized before the main function is executed. As in the previous example we clearly recognize that one int has been allocated on the heap.

Initial heap state:
0 bytes in 0 Free Blocks.
4 bytes in 1 Normal Blocks.
11426 bytes in 58 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 11430 bytes.
Total allocations: 16154 bytes.
Press any key to continue

Block types

Until now we have only discussed the "normal" blocks. It is time to have a look at the other block types and describe their purpose.

Block TypeDescription
_NORMAL_BLOCKObjects that you allocate on the heap will usually be put in blocks of this type unless you have temporary disabled the allocation tracking (see the _IGNORE_BLOCK type) or have explicitly specified a block type.
_CRT_BLOCKThe run-time library itself needs to allocate objects on the heap. The blocks allocated this way are marked as _CRT_BLOCK blocks so that they can be distinguished from the other blocks. The _CRT_BLOCK blocks are ignored by the memory leak detection functions and other operations.
_CLIENT_BLOCKAn application can keep track of specific blocks by using the _CLIENT_BLOCK type. This requires the application to explicitly use the _malloc_dbg function to specify the block type.
_FREE_BLOCKBy default freed blocks are removed from the heap. It is possible to change this default so that freed blocks are kept in the heap. In this case the freed blocks have the _FREE_BLOCK type.
_IGNORE_BLOCKIt is possible to temporarily disable the allocation tracking so that blocks allocated during that period aren't taken into account when doing leak detection. Blocks that must be ignored have the _IGNORE_BLOCK type. The _CrtSetDbgFlag with the _CRTDBG_ALLOC_MEM_DF flag is used to turn allocation tracking on on off.

The _IGNORE_BLOCK type

The following example shows how to use "ignore" blocks. This example can be downloaded from the Downloads page. In this example we disable the allocation tracking, allocate an int then we re-enable the tracking and allocate a second int. In the output we check that only the second int appears in the list of "normal" blocks.

#include "Main.h"
#include <stdio.h>
#include <crtdbg.h> // Header file for the debug heap API

int main(int argc, char** argv)
{
   // Send all reports to STDOUT
   _CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE );
   _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDOUT );
   _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE );
   _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDOUT );
   _CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_FILE );
   _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDOUT );

   // Get the initial state of the heap and display it
   printf("Initial heap state:\r\n");
   DisplayHeapState();

   // Disable allocation tracking
   int flags = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
   flags &= ~_CRTDBG_ALLOC_MEM_DF;
   _CrtSetDbgFlag(flags);

   void* dummy1 = new int;

   // Get the state of the heap and display it
   printf("\r\nHeap with int allocated while tracking disabled state:\r\n");
   DisplayHeapState();

   // Enable allocation tracking
   flags = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
   flags |= _CRTDBG_ALLOC_MEM_DF;
   _CrtSetDbgFlag(flags);

   void* dummy2 = new int;

   // Get the state of the heap and display it
   printf("\r\nHeap with int allocated after tracking reenabled state:\r\n");
   DisplayHeapState();

   delete dummy1;
   delete dummy2;

   // Get the final state of the heap and display it
   printf("\r\nFinal heap state:\r\n");
   DisplayHeapState();

   return 0;
}

void DisplayHeapState()
{
   _CrtMemState state;
   _CrtMemCheckpoint(&state);
   _CrtMemDumpStatistics(&state);
}

The output of this program is reproduced below. As expected we see that the "normal" blocks count remains 0 after we have allocated the first int (dummy1) since we disabled the tracking. The second int (dummy2), allocated after we re-enabled the tracking appears in the list.

The output shows something unexpected though. The number of "ignore" blocks remains 0 all the time. Shouldn't we have a count of 1 block of 4 bytes after we allocate dummy1? The fact is that "ignore" blocks are on the heap but are ignored by the heap debug functions. So despite the fact that it looks like these blocks are reported by the _CrtMemDumpStatistics function the result is meaningless.

Initial heap state:
0 bytes in 0 Free Blocks.
0 bytes in 0 Normal Blocks.
11417 bytes in 58 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 11417 bytes.
Total allocations: 16135 bytes.

Heap with int allocated while tracking disabled state:
0 bytes in 0 Free Blocks.
0 bytes in 0 Normal Blocks.
11417 bytes in 58 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 11417 bytes.
Total allocations: 16135 bytes.


Heap with int allocated after tracking reenabled state:
0 bytes in 0 Free Blocks.
4 bytes in 1 Normal Blocks.
11417 bytes in 58 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 11421 bytes.
Total allocations: 16139 bytes.


Final heap state:
0 bytes in 0 Free Blocks.
0 bytes in 0 Normal Blocks.
11417 bytes in 58 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 11421 bytes.
Total allocations: 16139 bytes.
Press any key to continue

Bibliography

  1. The CRT Debug Heap documentation in the MSDN Library

blog comments powered by Disqus

Copyright(c) 2006-2017 Xavier Leclercq | Privacy policy

Home
Contact Us
Search