Memory leaks are a problem that any developer may face at some time, and leaks have their own mysterious ways of showing up. The most common is Dr Watson errors on Windows and core dumps on Unix systems. A message such as Access Violation or Segmentation Violation happens when a pointer tries to access a memory location outside the process data segment. The operating system raises the red flag and crashes the process.
Under ideal conditions, a pointer should not be accessing memory locations outside the address range of data segment. However, the programmer is only human and C/C++ pointers have humbled many. We forget to deallocate memory or use the pointer even after it has been deleted or gone out of scope or leave a dangling pointer. The pointer, now, has a junk value. Sometimes, the pointer has a valid address but sometimes, fortunately or unfortunately (depending on which side you are), the address is outside the data segment and the program crashes and the problem is identified only after hours of re-reading the code.
The easiest solution to find this problem is to use software like Parasofts Insure++ or CompuWares BoundsChecker. But you can chase down memory leaks even if youre not using one of these tools. The simplest way to find a memory leak is with the Windows Task Manager. Run the application and monitor Memory Usage and Virtual Memory Size in the Process tab or use the Windows Resource kit. A continuous increase in virtual memory, after the application has allocated all the memory it needs, indicates a memory leak. If its a very small and subtle memory leak, it would take some hours to identify, but its well worth the effort. The Process Viewer (PVIEWER.EXE) in Windows Resource Kit provides a lot of details about the process, but it needs to be refreshed to update the status. The advantage with the Task Manager is that it updates automatically.
Now that we know that the program has a memory leak, we need to find it in order to fix it. My favorite tool is the Visual C++ Debug version of the C Runtime Library. It provides several functions to dump heap snapshots and track down memory leaks. The best part is that these functions are removed during preprocessing, if _DEBUG is not defined. So, just change the Build configuration from Debug to Release and the executable does not have these functions.
There are many useful functions in the Debug Library, but I am going to list the most important and the ones required to identify a memory leak. Example 1 shows a small implementation of the Debug functions.
This program has a very obvious memory leak. I have used three debug functions here, which are the minimum required to determine a memory leak. _CrtSetReportMode() and _CrtSetReportFile() are used to define the destination of the warnings, errors, and assertions. In Example 1, the report mode is set to write to a file. The report file is then set to send the output to stdout. _CrtDumpMemoryLeaks() determines if a memory leak has occurred since the start of program execution. It dumps all information for a block that is not deallocated. Example 1 results in the output in Example 2.
The output shows that I missed deallocating a 100 byte block and the data in that block. Since this was a very small program, it was very easy to identify the memory leak. But, in thousands of lines of code, its not going to be so easy. There are more Debug functions that would make the task easier.
Besides forgetting to deallocate memory, another problem is writing beyond the allocated memory. This causes memory corruption. And the Debug version of C Runtime library has just the function we need: _CrtCheckMemory(). This function validates the integrity of the memory blocks allocated by the debug heap manager. Consider this example:
char *ptr2; ptr2 = new char; // copying 12 char in string for 10 strcpy(ptr2,"ABCDEFGHIJKL"); _CrtCheckMemory();
The resulting output of this code would be:
memory check error at 0x00431CBA = 0x4B, should be 0xFD. memory check error at 0x00431CBB = 0x4C, should be 0xFD. memory check error at 0x00431CBC = 0x00, should be 0xFD. DAMAGE: after Normal block (#43) at 0x00431CB0. Normal located at 0x00431CB0 is 10 bytes long.
Other functions which I have found useful are _CrtMemCheckpoint(), _CrtMemDifference(), and _CrtMemDumpAllObectsSince(). _CrtMemCheckpoint() generates a snapshot of the state of the debug heap. This function has maximum use when the snapshot is taken at two different places in the program execution. The difference, using _CrtMemDifference(), will tell if there was a difference in heap usage at the two locations. This helps to identify the guilty section of the code:
_CrtMemState sh1, sh2, sh_diff; char *ptr1 = new char; _CrtMemCheckpoint(&sh1); char *ptr2 = new char; _CrtMemCheckpoint(&sh2); _CrtMemDifference(&sh_diff, &sh1, &sh2); _CrtMemDumpStatistics(&sh_diff);
_CrtMemDumpAllObjectsSince() is used by _CrtDumpMemoryLeaks() to determine if there was a memory leak. _CrtMemDumpAll-ObjectsSince() displays the debug heap from the start of program execution if NULL is passed or the status of the heap from the checkpoint or last heap snapshot if the checkpoint is passed.
Listing 1 shows the three examples with comments. Using the Task Manager and Process Viewer in conjunction with the Debug Version of C Runtime library will reduce a lot of pain and frustration caused by memory leaks and memory corruption.
Sumit Agarwal has been working on C++ on UNIX and Windows for the past seven years and presently works for HCL Perot Systems just outside Philadelphia. He can be reached at [email protected].