Now Where Did I Allocate That?!

Paul Laska 10:07 am on March 10, 2011 Counting comments...

Welcome to my very first ever blog, hopefully you enjoy it and find it useful.

Figuring out where a memory allocation was made doesn’t have to be difficult, and with a little effort, it isn’t.  Most game studios’ code I’ve worked with have their own version of memory tracking and memory allocators, usually based off of Doug Lea’s work (typedef struct TAllocationHeader { const char* filename; unsigned int line; unsigned int size; } TALLOCATION_HEADER;

Next take a look at the function signatures for malloc, calloc, realloc, memalign, new , free and delete.  Each method will need to be wrapped so the allocation structure can be attached to the allocated memory and filled in with the filename and line number information.  There are other methods, and other variations of the ones listed here, but extending the methodology is left for the reader.

void* malloc(size_t n)
 
  void* calloc(size_t n_elements, size_t element_size)
 
  void* realloc(void* p, size_t n)
 
  void* memalign(size_t alignment, size_t n)
 
  void* operator new(size_t size)
 
  void* operator new(size_t size, void* placement)
 
  void* operator new[](size_t size)
 
  void* operator new[](size_t size, void* placement)
 
  void free(void* p)
 
  void operator delete(void* p)
 
  void operator delete(void* p, void* placement)
 
  void operator delete[](void* p)
 
  void operator delete[](void* p, void* placement)

To wrap the methods create new names for the C methods, and overload the operators for the C++ methods, for example:

void* tracked_malloc(size_t n, const char* filename, unsigned int line)
 
  void* operator new(size_t, const char* filename, unsigned int line)

(Similarly add the “const char* filename, unsigned int line” to all the other method parameter lists)

Something I’ve found useful, and some people will probably boo and hiss about it, is to use preprocessor macros to make removing the filename and line easy during production builds.

#if DEBUG
 
  #define TRACKING_INFO    , const char* filename, unsigned int line
 
  #else
 
  #define TRACKING_INFO
 
  #endif

Then the method signatures end up getting declared like so:

void* tracked_malloc(size_t n    TRACKING_INFO)
 
  void* operator new (size_t n    TRACKING_INFO)

Notice there is no comma after the “size_t n”, because it is already included in the DEBUG version of TRACKING_INFO, or a complete blank in the non-DEBUG version. 

In the implementation of each allocation method increase the requested size by the size of the allocation structure.  Allocate the memory at the increased size.  Cast the allocated memory pointer to an allocation structure. Copy the filename parameter to the allocation structure’s filename pointer member. Copy the line parameter into the allocation structure’s line member. Copy the increased size into the allocation structure’s size member.  Move the pointer forward by the size of the allocation structure, and return the new address.

For the de-allocation methods, move the pointer back to the beginning of the allocation structure and de-allocate.

After the allocation and de-allocation methods are in place, switch all the standard method calls to the tracked methods.  Where filename and line are to be passed use the C and C++ defined __FILE__ and __LINE__ preprocessor macros.

Now any time you find an allocation that isn’t free when you think it should be, just move the pointer back by the size of the allocation structure, cast the pointer to an allocation structure and read where it was allocated.

Additional Notes:

“But the free and delete methods didn’t use the new parameters?”

Thank you for noticing.  These are actually something that can be used for tracking allocations and de-allocations if you implement and insert a reporting method call in each allocation and de-allocation method.

More Preprocessor Lovin’

Another way to ease use via the preprocessor is to have it remove the __FILE__ and __LINE__ information in production builds.  For example:

#if DEBUG
 
  #define TRACKING_PARAMS    , ___FILE__, __LINE__
 
  #else
 
  #define TRACKING_PARAMS
 
  #endif

Then the calls to your tracked methods would look like so: