See what's going on with flipcode!




This section of the archives stores flipcode's complete Developer Toolbox collection, featuring a variety of mini-articles and source code contributions from our readers.

 

  Detecting Memory Leaks
  Submitted by



The Problem

Paul Nettle's recent 'Ask Midnight' topic has brought up a few problems that I also ran into when starting on a new library. The problem centers around wanting to get the file and line number where an allocation occurred.

In order to do this, a macro is needed which includes the compiler defined __FILE__ and __LINE__ macros. Debug versions of malloc and its variants are written to take these as parameters. Finally a macro is used to hide the __FILE__ and __LINE__ macros from the user:



#define malloc(s) _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__) 




This works well, assuming macros like this are added to a common header and included by all of your source files.

However, a (big) problem arises when you also #define new to include this leak detection support:



#define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__)
#define new DEBUG_NEW 




The problem occurs when you include anything after this code that overloads operator new (global or per class). The compiler chokes and you're basically left removing the #define above and adding it to each .cpp file like so:

#include "..."
#include "..."

#ifdef _DEBUG #define new DEBUG_NEW #endif

...code..




This is exactly what MFC does to support memory leak detection. At the top of each ClassWizard generated .cpp file, you'll find the #define above.

Why this solution isn't all that great:

1. If you forget to add those lines to your cpp file, you won't be checking for new/delete leaks inside that file.

2. If a class overloads operator new, but not the specific format that includes parameters for the filename and linenumber, your code is not going to compile. If this is from a 3rd party lib, your only choice is to remove the #define and hope your code doesn't leak.

One Solution

The following is by no means the optimal solution. I personally haven't found an optimal solution, so here's what works for me. Note: I will be using the MS VC++ crtdbg lib for this, see below for platform independent ideas.

First, in a common (engine-wide/library-wide) include file add the follow at the beginning:



#ifdef _DEBUG
  #define _CRTDBG_MAP_ALLOC
  #include <stdlib.h
  #include <crtdbg.h
#endif

...all other global includes...




Next, I will assume a two letter prefix for my library of 'me' (for 'My Engine'). If you don't like this convention, then use all caps, but I strongly suggest this, as namespaces don't exist for #defines. Add the following to your common include file:

#ifdef _DEBUG
  #define meMalloc(s)       _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__)
  #define meCalloc(c, s)    _calloc_dbg(c, s, _NORMAL_BLOCK, __FILE__, __LINE__)
  #define meRealloc(p, s)   _realloc_dbg(p, s, _NORMAL_BLOCK, __FILE__, __LINE__)
  #define meExpand(p, s)    _expand_dbg(p, s, _NORMAL_BLOCK, __FILE__, __LINE__)
  #define meFree(p)         _free_dbg(p, _NORMAL_BLOCK)
  #define meMemSize(p)      _msize_dbg(p, _NORMAL_BLOCK)

#define meNew new(_NORMAL_BLOCK, __FILE__, __LINE__) #define meDelete delete

// Set to dump leaks at the program exit. #define meInitMemoryCheck() \ _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF)

#else #define meMalloc malloc #define meCalloc calloc #define meRealloc realloc #define meExpand _expand #define meFree free #define meMemSize _msize

#define meNew new #define meDelete delete

#define meInitMemoryCheck()

#endif




And, as you've probably guessed by now, use meMalloc() everywhere you were using malloc(), meNew in place of new, etc. If you've written a ton of code already, I suggest using Find In Files -- yes it isn't fun, but neither is wasting hours tracking down memory leaks. If you're starting from scratch, then it's obviously easier to get accustomed to. Adding meMalloc, meFree, etc to "Microsoft Visual Studio\Common\MSDev98\Bin\usertype.dat" and setting a unique color for "Tools\Options\Format\User Defined Keywords" can also help the transition.



Finally, in your application, simply call 'meInitMemoryCheck();' at the beginning of your program. You should also place this in ANY DLL that uses these functions -- either in an 'Initialize()' function that your app calls or in a small class in one .cpp file (per DLL) like this:



class MemoryCheckInitializer()
{
  MemoryCheckInitializer()
  {
    meInitMemoryCheck();
  }
};
static MemoryCheckInitializer mci_; 




When your program exits, it will spew any leaks into the 'Output/Debug' window. Double click on a line in the output to jump to the line in your source code.

Win32 users should be sure to read up on the other _Crt* functions available to them. There are also several MSDN articles regarding crtdbg support. Non-win32 users can replace the _malloc_dbg functions with code similar to Paul Nettle's.

Example Source Code: MemLeakTest.cpp (2k)

The zip file viewer built into the Developer Toolbox made use of the zlib library, as well as the zlibdll source additions.

 

Copyright 1999-2008 (C) FLIPCODE.COM and/or the original content author(s). All rights reserved.
Please read our Terms, Conditions, and Privacy information.