Wednesday, December 28, 2011

Useful Trick for Debugging Memory Leaks in C++ Code

The summer of 2010 was fucking awesome. I was living on campus at my university, taking two classes (neither of which started before 6:00pm), and lived with my friends in the house of a fraternity that we didn't belong to. I was waking up at 7:00am every day to run and had obscene amounts of free time.

I was also building a 2D video game engine from scratch in my spare time. I didn't have any serious intentions for the project, but it was something to do and was a great learning experience. From that project, I learned how critical impeccable code organization is, I learned tricks such as using pointers to class member functions, and I had a blast doing it.

Somewhere along the line, I was reading about C++'s ability to overload the new and delete operators, and in the process came up with a great, simple trick for detecting memory leaks. By overloading the memory allocation operators to keep track of the total amount of memory allocated by the program at any given time, one need only check, at the conclusion of the program, how much memory remains allocated. If the value is anything greater than 0, you've got memory leaking.

"But wait!" you say, "How do I know how much memory has been freed by each delete call? The operator delete signature doesn't give me that information!"


A valid objection. Here's the trick: Every time your custom new function gets called, use malloc() to dynamically overallocate the memory requested by exactly the size of a size_t variable. Store the amount of memory which is being allocated at the address returned by malloc(), and return the address which is sizeof(size_t) bytes after the address returned by malloc()! This way, when delete is called, you need only look sizeof(size_t) bytes backwards to determine how much memory was originally allocated.


I'll provide source code below. Note the use of a macro _TRACKMEMORY which can be switched on and off; although this trick is great for debugging, it does of course allocate more memory than is necessary, and thus it would not be a good idea to compile the code into a release build of a binary.



#define _TRACKMEMORY
#ifdef _TRACKMEMORY
   unsigned long totalAllocation = 0;
   void* operator new(size_t sz)
   {
      void* storage = malloc (sz + sizeof(size_t));   
      totalAllocation += sz;
      *((size_t*)storage) = sz;
      return ((char*)storage) + sizeof(size_t);
   }
   void operator delete (void* ptr)
   {
      totalAllocation -= *(size_t*)((char*)ptr - sizeof(size_t));
      free(((char*)ptr) - sizeof(size_t));
   }
#endif


unsigned long GetAllocatedMemory( void )
{
   #ifndef _TRACKMEMORY
      return 0;
   #else
      return totalAllocation;
   #endif
}

Ideally of course, you simply compile this into a separate source file (I like to call it TrackMemory.cpp). From there, you just call GetAllocatedMemory() every time you want to know the amount of memory currently allocated by the program; of course, it is necessary to expose the declaration of GetAllocatedMemory() through a header file before calling it. This may go without saying, but of course this trick will not necessarily work as intended if your program is allocating memory through some method besides using the global new operator (malloc, class-specific new operator overloads, etc).

Let me know if you think this is as useful as I do.

7 comments:

  1. I don't know if you are familiar with them, but there are some tools already available that will help you with these problems.
    They'll even tell you where the leaking memory was allocated.
    Libumem on Solaris, glibc on linux (valgrind is another option). There are also commercial tools that will do a good job at this. Some links:
    http://www.gnu.org/software/libc/manual/html_node/Allocation-Debugging.html#Allocation-Debugging

    http://blogs.oracle.com/pnayak/entry/finding_memory_leaks_within_solaris

    http://msdn.microsoft.com/en-us/library/x98tx3cf(v=vs.80).aspx

    These are just the ones I've used. There are plenty of others.

    ReplyDelete
  2. As far as i know the new operator implicitly calls the default constructor with objects but malloc doesn't. There is the same effect with delete and free.

    ReplyDelete
  3. Yep, I'm aware that there are (far superior) tools for doing this. Still, I think that the example is instructive in that it provides a nice motivation for operator overloading and forces the programmer to think about memory on a low level, which I think is good for C++ programmers. I'm quite aware that I am in some sense re-inventing the wheel here, but I do feel that my method is a simple solution for simple projects. More importantly still, it's simple enough for its workings to be understandable by those less experienced with programming =)

    Yes, the new operator calls the default constructor and malloc does not (as with delete and free). This is our intended goal: We are simply trying to allocate the memory. The constructor will be implicitly called once we exit the new operator whether we like it or not.

    ReplyDelete
  4. Ok my bad, i was thinking that it's your job to call the default constructor when you overload the new operator.

    Thanks

    ReplyDelete
  5. Confusingly "the new operator" and "operator new" are two different things in C++. The former allocates memory using the latter and then calls the constructor. The latter only allocates memory and can be overriden by the user.

    ReplyDelete
  6. Very good point, Sebastian. You could say that about any overloadable operator in C++ though, no? For instance, one could argue that "the + operator" and "operator +" are different notions.

    ReplyDelete
  7. Well not really, because + adds two things, and operator+ implements the + operator. The correspondence between new and operator new is much more complicated. They really should've called the latter something different IMO to reduce confusion. It's not really new'ing up an object, it's just a raw memory allocator.

    ReplyDelete