More on dealing with memory leaks

Mismatch in memory usage patterns across APIs

Allocations that use many different libraries or are composed of different modules may have different rules about who cleans up memory allocations and when. Memory leaks can occur if developers miscommunicate or fail to document the rules around memory allocation patterns. One library may keep track of allocated pointers and clean them up in a “finalize” routine, and another may assume that the calling function will call free() on any pointers that it passes up to the calling functions.

Similarly, one input function may expects the calling function to create a buffer of a given size, and another function may allocate space as needed. If you prepare a buffer and pass it to a function that expects to allocate its own buffer, your program probably won’t crash but you will get a leak.

The same concern applies to C++ code and can be even harder to track down through all the layers of class inheritance.

Unexpected loops (code reuse)

It is not uncommon that code is written initially to be used to solve a single specific problem.  If the code does that well it may be drafted to solve a wider range of related problems. When that re-factoring and re-use occurs,  the life cycle of the memory allocation, originally clear in the programmer’s mind when the function was written, can be obscured.  When this happens it is easy to do something like

int afunc(int **a){
int i;
*a=(int*)(malloc(sizeof(int)*N));
if (*a==0){
return 0; /*we were thinking about the rules when we wrote this bit of code so we checked for a failed malloc() */
}
for(i=0;i<N;i++){
(*a)[i]=i*i; /* or some other more interesting math */
}
return 1;
}


void oldcallingfunc(){
int * b;
if(afunc(&b)){
/* do something with b*/
}
free(b);
}


void newcallingfunc(){
int * b;
int i=0;
while (afunc(&b) && (i < 10) ){
/* do something with b*/
i++;
}
free(b);
}

This leaves the program with many leaks.

Pointer errors

Another cause of leaks is to attempt to deallocate memory with the wrong information. One of the more dangerous ways of generating memory leaks is to attempt to free a block with the wrong pointer. Depending on how the incorrect pointer is generated, this can cause a number of other serious side effects. Using a stale pointer, for instance,  causes a “double free” condition.

Using pointer arithmetic to maneuver within a block can cause a modified internal block pointer to be inadvertently used for the deallocation process, causing an “interior pointer” error. It is possible to avoid or at least trap some of such modified pointer misuse by NULLing out pointers when the block is released, but this can be difficult to do when pointers are aliased in any way.

In another scenario, pointers are so poorly maintained that a program picks up a valid block address, but gets it from a completely different allocation request and releases it.  In this case, the code making the allocation request may compete with another section of code that subsequently allocated the same region of the heap memory, resulting in corrupted data for one or both sections.

Note that in many of these situations, an error (or fault) is generated when invalid data is used. In every case, however, the original, intended block is not released, thus resulting in a leak.

Failure in reference counting

The pointer to a block of memory that you have allocated serves as a reference to that block. If you use that block of memory in fairly limited ways without ever duplicating the reference, it is generally straightforward to clean up the block, calling free with the pointer value when the program is done with that bit of temporary storage.

However, you may want to make copies of that pointer and associate the contents of the memory with multiple concepts (or objects) in your code. This is more complicated: when you call free you must ensure that there are no longer any active references to the block of memory. The standard method is to use a reference counter, a second variable which tracks the number of copies of the pointer.  In C++ code this is often done by creating an object that includes both the pointer and the reference count, and defining the basic operations for this object to standardize incrementing and decrementing the counter.

This can go wrong in multiple ways. You probably don’t want to make every pointer in your program a reference-counted pointer so there is the danger of omitting reference counting on a pointer that needs it. There is also the danger of failing to use the standard mechanism for incrementing and decrementing the reference counter. When your code makes the transition from single-threaded to multi-threaded there is the risk of a race condition around the reference counting, so exclusion techniques must be applied to ensure that incrementing and decrementing the counter is never done in more than one thread at a time.

Logic error in destructors

Neglecting to add member variables to class destructors is the most obvious cause of leaking memory in object-oriented programming languages. Unfortunately it is seldom the case that the memory leaks in your program are caused in such a simple way.  In all but the most trivial applications, for every member variable it is not always clear which object owns it. Many types of large objects like bitmaps, buffers, or strings need to be referenced from many places instead of making multiple copies.

Comments like “/** not owned */” make things more clear, but cannot be blindly trusted by another person looking at the code, since the more informative and detailed that comments are, the sooner they become obsolete when changes are made.

Use references instead of pointers whenever you can. They leave nothing to the imagination of the person trying to decide whether or not the variable should be mentioned in the class’s destructor.

It takes relatively little time to make sure newly written member variables are closed and deleted properly in the appropriate class’s destructor.  It can take a lot more time to find and correct the failure after the fact.

PurifyPlus Memory Leak Properties screenshot

PurifyPlus Memory Leak Properties screenshot

Failure to “clean up”

Fundamentally, avoiding memory leaks is a simple matter of  cleaning up after yourself.  After all, your mother always told you to put things away when you are done with them, right? And that was when life was quite a bit simpler! It’s all really a matter of getting organized.

Obviously, when one spends so much energy and creativity creating a clever application that does what it is supposed to do (and with no apparent errors), it is often hard to go back after the fact and revisit the memory management handling “just in case.”  But most often, a little more attention to detail in handling memory up front can make a big difference in the end. Thankfully there are many memory-checking tools that can detect and report most shortcomings in memory organization.

[Prev: Key issues and causes] [Next: Strategies]