Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com
Answertopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions
Privacy Policy

  




 

 

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

Finding memory leaks

The following straightforward debugging techniques are explained in Volume 1:

1.  For array bounds checking, use the Array template in C16:Array3.cpp of Volume 1 for all arrays. You can turn off the checking and increase efficiency when you re ready to ship. (Although this doesn t deal with the case of taking a pointer to an array.)

2.  Check for non-virtual destructors in base classes.

Tracking new/delete and malloc/free

Common problems with memory allocation include mistakenly calling delete for memory that s not on the free store, deleting the free store more than once, and, most often, forgetting to delete a pointer. This section discusses a system that can help you track down these kinds of problems.

As an additional disclaimer beyond that of the preceding section: because of the way we overload new, the following technique may not work on all platforms, and will only work for programs that do not call the function operator new( ) explicitly. We have been quite careful in this book to only present code that fully conforms to the C++ Standard, but in this one instance we re making an exception for the following reasons:

1.  Even though it s technically illegal, it works on many compilers.[29]

2.  We illustrate some useful thinking along the way.

 

To use the memory checking system, you simply include the header file MemCheck.h, link the MemCheck.obj file into your application to intercept all the calls to new and delete, and call the macro MEM_ON( ) (explained later in this section) to initiate memory tracing. A trace of all allocations and deallocations is printed to the standard output (via stdout). When you use this system, all calls to new store information about the file and line where they were called. This is accomplished by using the placement syntax for operator new.[30] Although you typically use the placement syntax when you need to place objects at a specific point in memory, it can also create an operator new( ) with any number of arguments. This is used in the following example to store the results of the __FILE__ and __LINE__ macros whenever new is called:

//: C02:MemCheck.h
#ifndef MEMCHECK_H
#define MEMCHECK_H
#include <cstddef> // For size_t
 
// Usurp the new operator (both scalar and array versions)
void* operator new(std::size_t, const char*, long);
void* operator new[](std::size_t, const char*, long);
#define new new (__FILE__, __LINE__)
 
extern bool traceFlag;
#define TRACE_ON() traceFlag = true
#define TRACE_OFF() traceFlag = false
 
extern bool activeFlag;
#define MEM_ON() activeFlag = true
#define MEM_OFF() activeFlag = false
 
#endif // MEMCHECK_H ///:~
 

It is important to include this file in any source file in which you want to track free store activity, but include it last (after your other #include directives). Most headers in the standard library are templates, and since most compilers use the inclusion model of template compilation (meaning all source code is in the headers), the macro that replaces new in MemCheck.h would usurp all instances of the new operator in the library source code (and would likely result in compile errors). Besides, you are only interested in tracking your own memory errors, not the library s.

In the following file, which contains the memory tracking implementation, everything is done with C standard I/O rather than with C++ iostreams. It shouldn t make a difference, since we re not interfering with iostreams use of the free store, but when we tried it, some compilers complained. All compilers were happy with the <cstdio> version.

//: C02:MemCheck.cpp {O}
#include <cstdio>
#include <cstdlib>
#include <cassert>
#include <cstddef>
using namespace std;
#undef new
 
// Global flags set by macros in MemCheck.h
bool traceFlag = true;
bool activeFlag = false;
 
namespace {
 
// Memory map entry type
struct Info {
void* ptr;
const char* file;
long line;
};
 
// Memory map data
const size_t MAXPTRS = 10000u;
Info memMap[MAXPTRS];
size_t nptrs = 0;
 
// Searches the map for an address
int findPtr(void* p) {
for(size_t i = 0; i < nptrs; ++i)
if(memMap[i].ptr == p)
return i;
return -1;
}
 
void delPtr(void* p) {
int pos = findPtr(p);
assert(pos >= 0);
// Remove pointer from map
for(size_t i = pos; i < nptrs-1; ++i)
memMap[i] = memMap[i+1];
--nptrs;
}
 
// Dummy type for static destructor
struct Sentinel {
~Sentinel() {
if(nptrs > 0) {
printf("Leaked memory at:\n");
for(size_t i = 0; i < nptrs; ++i)
printf("\t%p (file: %s, line %ld)\n",
memMap[i].ptr, memMap[i].file, memMap[i].line);
}
else
printf("No user memory leaks!\n");
}
};
 
// Static dummy object
Sentinel s;
 
} // End anonymous namespace
 
// Overload scalar new
void*
operator new(size_t siz, const char* file, long line) {
void* p = malloc(siz);
if(activeFlag) {
if(nptrs == MAXPTRS) {
printf("memory map too small (increase MAXPTRS)\n");
exit(1);
}
memMap[nptrs].ptr = p;
memMap[nptrs].file = file;
memMap[nptrs].line = line;
++nptrs;
}
if(traceFlag) {
printf("Allocated %u bytes at address %p ", siz, p);
printf("(file: %s, line: %ld)\n", file, line);
}
return p;
}
 
// Overload array new
void*
operator new[](size_t siz, const char* file, long line) {
return operator new(siz, file, line);
}
 
// Override scalar delete
void operator delete(void* p) {
if(findPtr(p) >= 0) {
free(p);
assert(nptrs > 0);
delPtr(p);
if(traceFlag)
printf("Deleted memory at address %p\n", p);
}
else if(!p && activeFlag)
printf("Attempt to delete unknown pointer: %p\n", p);
}
 
// Override array delete
void operator delete[](void* p) {
operator delete(p);
} ///:~
 

The Boolean flags traceFlag and activeFlag are global, so they can be modified in your code by the macros TRACE_ON( ), TRACE_OFF( ), MEM_ON( ), and MEM_OFF( ). In general, enclose all the code in your main( ) within a MEM_ON( )-MEM_OFF( ) pair so that memory is always tracked. Tracing, which echoes the activity of the replacement functions for operator new( ) and operator delete( ), is on by default, but you can turn it off with TRACE_OFF( ). In any case, the final results are always printed (see the test runs later in this chapter).

The MemCheck facility tracks memory by keeping all addresses allocated by operator new( ) in an array of Info structures, which also holds the file name and line number where the call to new occurred. To prevent collision with any names you have placed in the global namespace, as much information as possible is kept inside the anonymous namespace. The Sentinel class exists solely to call a static object destructor as the program shuts down. This destructor inspects memMap to see if any pointers are waiting to be deleted (indicating a memory leak).

Our operator new( ) uses malloc( ) to get memory, and then adds the pointer and its associated file information to memMap. The operator delete( ) function undoes all that work by calling free( ) and decrementing nptrs, but first it checks to see if the pointer in question is in the map in the first place. If it isn t, either you re trying to delete an address that isn t on the free store, or you re trying to delete one that s already been deleted and removed from the map. The activeFlag variable is important here because we don t want to process any deallocations from any system shutdown activity. By calling MEM_OFF( ) at the end of your code, activeFlag will be set to false, and such subsequent calls to delete will be ignored. (That s bad in a real program, but our purpose here is to find your leaks; we re not debugging the library.) For simplicity, we forward all work for array new and delete to their scalar counterparts.

The following is a simple test using the MemCheck facility:

//: C02:MemTest.cpp
//{L} MemCheck
// Test of MemCheck system.
#include <iostream>
#include <vector>
#include <cstring>
#include "MemCheck.h" // Must appear last!
using namespace std;
 
class Foo {
char* s;
public:
Foo(const char*s ) {
this->s = new char[strlen(s) + 1];
strcpy(this->s, s);
}
~Foo() { delete [] s; }
};
 
int main() {
MEM_ON();
cout << "hello" << endl;
int* p = new int;
delete p;
int* q = new int[3];
delete [] q;
int* r;
delete r;
vector<int> v;
v.push_back(1);
Foo s("goodbye");
MEM_OFF();
} ///:~
 

This example verifies that you can use MemCheck in the presence of streams, standard containers, and classes that allocate memory in constructors. The pointers p and q are allocated and deallocated without any problem, but r is not a valid heap pointer, so the output indicates the error as an attempt to delete an unknown pointer:

hello
Allocated 4 bytes at address 0xa010778 (file: memtest.cpp, line: 25)
Deleted memory at address 0xa010778
Allocated 12 bytes at address 0xa010778 (file: memtest.cpp, line: 27)
Deleted memory at address 0xa010778
Attempt to delete unknown pointer: 0x1
Allocated 8 bytes at address 0xa0108c0 (file: memtest.cpp, line: 14)
Deleted memory at address 0xa0108c0
No user memory leaks!
 

Because of the call to MEM_OFF( ), no subsequent calls to operator delete( ) by vector or ostream are processed. You still might get some calls to delete from reallocations performed by the containers.

If you call TRACE_OFF( ) at the beginning of the program, the output is

hello
Attempt to delete unknown pointer: 0x1
No user memory leaks!
 
Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

 
 
   Reproduced courtesy of Bruce Eckel, MindView, Inc. Design by Interspire