multipart-mixed

Easy C++ Allocation Tracking

Programmers complain ad nauseum about the lack of garbage collection in C++, and about how hard it is to detect leaks during development. Here's some code which, used in conjunction with good unit tests, will help you quickly detect and isolate leaks.

Motivation

I'll keep this short -- you know the motivation already. There are two common issues causing memory leaks in C++ applications:

  • Allocating from the heap with new and forgetting to delete.
  • Using the wrong type of delete, e.g. allocating with array new (new[]) and deleting with normal delete.

There are a couple solutions. First, you could override operator new and operator delete in a base class common to all objects in your application. That'll help, but it won't catch allocations of standard types. Second, you could override the default operators. That's the approach I take here. (I suppose there's a third solution: don't write code with leaks.)

Implementation

First, download the full source code.

Note that I have tested this code on MacOS X and FreeBSD with the gcc compiler. If you're using Windows/MFC you'll probably want to use the MFC memory snapshot features instead of this code anyway.

I'll excerpt the key points here. Let's set up some globals for the stuff we're tracking. I use the STL map class to help match pointers and sizes. I use two of them, one for standard allocations and another for array allocations. Note on gTrackAllocation: this must be initialized to false because other globals/statics might allocate memory before the AllocationMap objects are initialized. At the start of your app code, set this variable to true.

size_t gAllocatedMemory = 0;
bool gTrackAllocation = false; /* IMPORTANT: set true when your app starts */
typedef map AllocationMap;
static AllocationMap gAllocationMapStandard;
static AllocationMap gAllocationMapArray;

Then I can use a common function for both new and new[], and just switch maps depending on which is called. Note that we need to turn off tracking before inserting into the map since that'll call new internally.

inline void* tracked_new(size_t size, AllocationMap &map) throw(bad_alloc)
{
    assert(size != 0);
    void* ptr = 0;

    if (gTrackAllocation)
    {
        gAllocatedMemory += size;
        ptr = malloc(size);

        gTrackAllocation = false;
        map[ptr] = size;
        gTrackAllocation = true;
    }
    else
    {
        ptr = malloc(size);
    }

    if (ptr == 0)
        throw bad_alloc();
    else
        return ptr;
}

Delete goes as you'd expect, and kindly asserts if you try to delete something that either doesn't exist (e.g. double-delete) or use the wrong kind of delete (array vs. standard). You can replace the assert with an exception throw appropriate to your application.

inline void tracked_delete(void* ptr, AllocationMap &map) throw()
{
    if (gTrackAllocation)
    {
        size_t size = map[ptr];
        assert(size != 0);
        gAllocatedMemory -= size;

        gTrackAllocation = false;
        map.erase(ptr);
        gTrackAllocation = true;
    }

    free(ptr);
}

Then just override new and delete:

void* operator new(size_t size) throw(bad_alloc)
{
    return tracked_new(size, gAllocationMapStandard);
}

void* operator new[](size_t size) throw(bad_alloc)
{
    return tracked_new(size, gAllocationMapArray);
}

void operator delete(void* ptr) throw()
{
    tracked_delete(ptr, gAllocationMapStandard);
}

void operator delete[](void* ptr) throw()
{
    tracked_delete(ptr, gAllocationMapArray);
}

Suggestions For Use

The best place to use this is during unit tests. (You do write unit tests, right?) Since proper unit test suites should have no side effects, it's very easy to look for increases in allocated memory after a suite runs. For example:

size_t startingMemory = gAllocatedMemory;

FooTestSuite.Run();

assert(gAllocatedMemory == startingMemory);

BarTestSuite.Run();

assert(gAllocatedMemory == startingMemory);

For production builds, I would #ifdef out the whole tracking framework.

Download Code

memory_tracking.zip