ArticleS
.
JamesGrenning
.
CppTestToolsMemoryLeakDetector
Edit Page:
!title CppTestTools Memory Leak Detector There are some elaborate memory leak checking capabilities built into the Microsoft visual c++ and studio debug libraries. There are others you can get to work with gcc. I’ve never had the time to figure them out. So, I implemented a very simple memory leak detector for gcc in the !-CppTestTools UnitTestHarness-!. If you look into the Platforms directory you can see how to do it with VC++. The gcc version overrides global operators new and delete operator. A new will increment a counter, a delete will decrement that same counter. The !-UnitTestHarness-! does a check point before and after each unit test to see that the test executing as balance between new and delete. It turns out there are some problems with this approach. Some of the standard library routines appear to leak. I am not sure if they are real leaks or not. One thing that seems to be happening is that on first use string and stringstream allocate something and do not delete it. The allocation seems to get reused so there is not a chronic leak. The way I check for chronic leaks it to have the UnitTestHarness run all the tests twice. Chronic leaks are reported in both runs. These phantom leaks are reported on the first pass only. {{{ class MemoryLeakWarning { public: static void Enable(); static void Report(); static void CheckPointUsage(); static bool UsageIsNotBalanced(); static const char* Message(); }; }}} You will see that !-UnitTestHarness-! declares this class. There is an implementation in Platforms/gcc and Platforms/VisualCpp. Build one or the other based upon your platform. The entry point for the !-UnitTestHarness calls MemoryLeakWarning::Enable()-!. Each unit test does this: {{{ CheckPointUsage SetUp Run the test’s body TearDown if (UsageIsNotBalanced()) generate a memory leak failure }}} !*> gcc !-MemoryLeakWarning-! Code #include "UnitTestHarness/MemoryLeakWarning.h" #include <stdlib.h> #include <stdio.h> static int allocatedBlocks = 0; static int initialBlocksUsed = 0; static int allocatedArrays = 0; static int initialArraysUsed = 0; static char message[30] = ""; void reportMemoryBallance() { int blockBalance = allocatedBlocks - initialBlocksUsed; int arrayBalance = allocatedArrays - initialArraysUsed; if (blockBalance == 0 && arrayBalance == 0) printf("No leaks detected\n"); else { if (blockBalance > 0) printf("Memory leak! %d blocks not deleted\n", blockBalance); if (arrayBalance > 0) printf("Memory leak! %d arrays not deleted\n", arrayBalance); if (blockBalance < 0) printf("More blocks deleted than newed! %d extra deletes\n", blockBalance); if (arrayBalance < 0) printf("More arrays deleted than newed! %d extra deletes\n", arrayBalance); printf("NOTE - some memory leaks appear to be allocated statics that are not released\n" " - by the standard library\n" " - Use the -r switch on your unit tests to repeat the test sequence\n" " - If no leaks are reported on the second pass, it is likely a static\n" " - that is not released\n"); } } void MemoryLeakWarning::Enable() { initialBlocksUsed = allocatedBlocks; initialArraysUsed = allocatedArrays; atexit(reportMemoryBallance); } void MemoryLeakWarning::Report() { if (initialBlocksUsed != allocatedBlocks || initialArraysUsed != allocatedArrays ) { printf("Memory new/delete imbalance after running tests\n"); } } static int blockUsageCheckPoint = 0; static int arrayUsageCheckPoint = 0; void MemoryLeakWarning::CheckPointUsage() { blockUsageCheckPoint = allocatedBlocks; arrayUsageCheckPoint = allocatedArrays; } bool MemoryLeakWarning::UsageIsNotBalanced() { if (blockUsageCheckPoint != allocatedBlocks) sprintf(message, "this test leaks %d blocks", allocatedBlocks - blockUsageCheckPoint); if (arrayUsageCheckPoint != allocatedArrays) sprintf(message, "this test leaks %d arrays", allocatedArrays - arrayUsageCheckPoint); return blockUsageCheckPoint != allocatedBlocks || arrayUsageCheckPoint != allocatedArrays; } const char* MemoryLeakWarning::Message() { return message; } void* operator new(size_t size) { allocatedBlocks++; return malloc(size); } void operator delete(void* mem) { allocatedBlocks--; free(mem); } void* operator new[](size_t size) { allocatedArrays++; return malloc(size); } void operator delete[](void* mem) { allocatedArrays--; free(mem); } *!!*> !-VisualCpp MemoryLeakWarning-! Code #include "UnitTestHarness/MemoryLeakWarning.h" #define CRTDBG_MAP_ALLOC #include <stdlib.h> #include <crtdbg.h> #include <windows.h> #ifdef _DEBUG #define SET_CRT_DEBUG_FIELD(a) \ _CrtSetDbgFlag((a) | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG)) #else #define SET_CRT_DEBUG_FIELD(a) ((void) 0) #endif void MemoryLeakWarning::Enable() { _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_DEBUG|_CRTDBG_MODE_FILE ); _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDERR ); _CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_DEBUG|_CRTDBG_MODE_FILE ); _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR ); _CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_DEBUG|_CRTDBG_MODE_FILE ); _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDERR ); SET_CRT_DEBUG_FIELD( _CRTDBG_LEAK_CHECK_DF ); } void MemoryLeakWarning::Report() { //windows reports leaks automatically when set up as above } _CrtMemState s1, s2, s3; void MemoryLeakWarning::CheckPointUsage() { _CrtMemCheckpoint( &s1 ); } bool MemoryLeakWarning::UsageIsNotBalanced() { _CrtMemCheckpoint( &s2 ); if (_CrtMemDifference( &s3, &s1, &s2) ) return true; else return false; } const char* MemoryLeakWarning::Message() { return "this test leaks"; } *! !commentForm !* Fri, 29 Apr 2005 02:22:52, Uwe, False positives Spurious leak reports are a common problem when using leak detectors on C++ code that makes use of the C++ standard library. There are two issues with typical implementations of the standard library: 1) Some objects created by the standard library are singletons rsp. static objects, e.g. cin and cout. They are never cleaned up. 2) Most standard library components, especially the containers, use the standard allocator by default if they need to allocate memory. The standard allocator is typically implemented in a way that it never returns memory. But, of course, it keeps track of that memory, so it is never lost. When either the same or another componenent needs memory again, it is available. An implementation might be prone to memory fragmentation problems, though I've never heard of this, but it won't produce leaks. The first issue is neither a leak nor a problem because the amount of memory is bounded - after all, it's about singletons. The second issue is not a leak because the memory, however much, is available to the program up to the very end. Of course, you might dislike the fact that a program doesn't return the memory, e.g. if a program needs a vast amount of memory for a short time only and then becomes parsimonious. If so, you can write and use your own allocator. Final note: Some people have strong opinions about this. "Whenever memory is allocated but not returned to the system, this is a leak per definition." That's like defining a leak as " whenever water from the sea enters the ship". Would you really accept an prohibition of showering, just because of some overzealous toolmakers? *! !* Sun, 1 May 2005 20:04:43, James Grenning, Who me, a zealot? I agree that items 1 and 2 you point out are not a problem that will lead to an out of heap crash. But those practices do make it difficult to see chronic leaks. I’ve got a work around in CppTestTools to help see the difference between chronic and false positives (one time memory leaks). Running the unit tests twice with out exiting main demonstrates is evidence that the leak is not chronic. This is not fool proof, but a step in the right direction. In test driven development I consider a leak to be a failure. Its like balancing a checkbook. I balance my checkbook, to make sure the money is being used with my knowledge. Ideally each unit test would balance the memory allocations checkbook. I'd like to make sure that my code is not irresponsible in dealing with memory. There are bilge pumps to keep the sea from sinking the boat. I'd say the designers of boats are very concerned about leaks :-) I bet most sailors prefer showers too. A boat is an open system when it comes to sea water and fresh water. A computer is not an open system when it comes to memory. A computer with limited memory space is not open to irresponsible use of memory. There is a fixed amount of memory (possibly scarce in embedded systems). Not cleaning up singletons is not a problem that leads to a crash, but it is a problem in making sure the memory usage books stay balanced. I just happen to believe that if I cause a memory leak, I want to know right away. The best time is during a run of my automated tests. It is easy to fix then, and customers don’t get angry. I'd rather not have to wait for the out of heap crash, calls from end users, a breach of safety and then some huge leak finding mission. Is that overzealous? I don’t. But, you did not mean me did you? My theory all along has been that these one time leaks are singletons or the like. I’d really like to know the root cause details. I’ve seen some odd things that don’t have that simple of an explanation. I’ll post some examples sometime soon. *! !* Tue, 28 Feb 2006 21:39:35, Frank Johnson, *!
Hints:
Use alt+s (Windows) or control+s (Mac OS X) to save your changes. Or, tab from the text area to the "Save" button!
Grab the lower-right corner of the text area to increase its size (works with some browsers).