Click here to Skip to main content
15,884,473 members
Articles / Programming Languages / C++

Check for and free memory leaks

Rate me:
Please Sign up or sign in to vote.
4.67/5 (9 votes)
24 Jul 200310 min read 152.5K   3.1K   53  
An article on detection and freeing of memory leaks
// The MemLeakCheck source code is by Robert Walker
// support@tunesmithy.co.uk

// To check for updates visit
// http://tunesmithy.co.uk/memleakcheck/index.htm

// You are free to do what you like with it including
// using it in commercial programs, and no need to publish
// changes you make. 

// Please keep this notice in your source code if practical
// so other developers who happen on it know where to go to 
// find out more and check for updates.

// I'll be interested to hear about any improvements
// you make :-).

// Robert

#include "MemLeakCheck.h"
#undef malloc
#undef calloc
#undef strdup
#undef realloc
#undef free
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <malloc.h>
typedef unsigned char byte;

#ifdef cl_mem_leak_diagnostics

struct _malloc_info
{
 int nrecord;// needs to be first
 const char *szFile;
 int iLine;
 int size;
};
typedef struct _malloc_info MallocInfo;

#else

typedef int MallocInfo;

#endif

#define MALLOC_INFO_SIZE sizeof(MallocInfo)

#define POINTER_ALLOC_INCR 1024

#ifdef MLC_USE_THREAD_LOCAL_VARIABLES
__declspec(thread) MallocInfo **pointers_allocated;
__declspec(thread) int npointers_allocated;
__declspec(thread) int max_pointers_allocated;
__declspec(thread) int ntotal_allocations;
__declspec(thread) double dmem_alloc;
 #ifdef _DEBUG 
  #ifdef cl_mem_leak_diagnostics
   __declspec(thread) static MallocInfo was_MI;
  #endif
 #endif
#else
MallocInfo **pointers_allocated;
int npointers_allocated;
int max_pointers_allocated;
int ntotal_allocations;
double dmem_alloc; // in case it goes out of range for longs
                   // - this is the total amount in all allocations made - 
                   // so if memory is allocted, freed, re-allocated again it adds to this each time

 #ifdef _DEBUG 
  #ifdef cl_mem_leak_diagnostics
   static MallocInfo was_MI; // so you can look at it in debugger 
  #endif
 #endif
#endif

#ifdef _DEBUG
 // #define DEBUG_P // Define this if you want to make sure that
 // the position of the pointer in the pointers_allocated[] array
 // remains unchanged throughout all the allocations
 // - means this array will gradually get larger as you
 // free and allocate more pointers - only 
 // needed if you step into this code to find out more
 // about what is happening in your allocations and frees
#endif

static int ntest_compactify_pointers_at=1024;

#ifndef cl_mem_leak_check

 #undef MemLeakCheckAndFree
 #undef SetMemLeakWarnProc
 #undef SetMemLeakLogProc
 #undef SetMemLeakAnticipateFreeProc

#endif

#ifndef cl_mem_leak_diagnostics

 #undef SetMemLeakWarnProc
 #undef SetMemLeakLogProc
 #undef SetMemLeakAnticipateFreeProc

#endif

#ifdef BUILD_MEMLEAK_CHECK_AS_DLL

// calling proc can set these to sidestep the problems involved
// in passing pointers to a dll that uses a different version of the
// C Run Time (CRT)
MEMLEAK_CHECK_WM_DLL_A void MEMLEAK_CHECK_WM_DLL_B set_mlc_malloc
(void *(__cdecl *new_mlc_malloc)(size_t size));

MEMLEAK_CHECK_WM_DLL_A void MEMLEAK_CHECK_WM_DLL_B set_mlc_calloc
(void *(__cdecl *new_mlc_calloc)(size_t num,size_t size));

MEMLEAK_CHECK_WM_DLL_A void MEMLEAK_CHECK_WM_DLL_B set_mlc_realloc
(void *(__cdecl *new_mlc_realloc)(void *p,size_t size));

MEMLEAK_CHECK_WM_DLL_A void MEMLEAK_CHECK_WM_DLL_B set_mlc_free
(void (__cdecl *new_mlc_free)(void *p));

MEMLEAK_CHECK_WM_DLL_A void MEMLEAK_CHECK_WM_DLL_B set_mlc_memcpy
(void *(__cdecl *new_mlc_memcpy)(void *dest,void *src,size_t count));

MEMLEAK_CHECK_WM_DLL_A void MEMLEAK_CHECK_WM_DLL_B set_mlc_strlen
(size_t (__cdecl *new_mlc_strlen)(const char *string));

MEMLEAK_CHECK_WM_DLL_A void MEMLEAK_CHECK_WM_DLL_B set_mlc_strcpy
(char *(__cdecl *new_mlc_strcpy)(char *strDestination,const char *strSource ));


#define MAKE_PROC_DEC_AND_SET_PROC(type,proccname,args)\
type (__cdecl *mlc_##proccname)args=proccname;\
\
MEMLEAK_CHECK_WM_DLL_A void MEMLEAK_CHECK_WM_DLL_B set_mlc_##proccname\
(type (__cdecl *new_mlc_##proccname)args)\
{\
 mlc_##proccname=new_mlc_##proccname;\
}\

MAKE_PROC_DEC_AND_SET_PROC(void *,malloc,(size_t size))
MAKE_PROC_DEC_AND_SET_PROC(void *,calloc,(size_t num,size_t size))
MAKE_PROC_DEC_AND_SET_PROC(void *,realloc,(void *p,size_t size))
MAKE_PROC_DEC_AND_SET_PROC(void,free,(void *p))
MAKE_PROC_DEC_AND_SET_PROC(void *,memcpy,(void *psrc,void *pdest,size_t size))
MAKE_PROC_DEC_AND_SET_PROC(char *,strcpy,(char *strDestination,const char *strSource))
MAKE_PROC_DEC_AND_SET_PROC(size_t,strlen,(const char *string))

#else

#define mlc_malloc malloc
#define mlc_free free
#define mlc_calloc calloc
#define mlc_realloc realloc
#define mlc_strdup strdup
#define mlc_strcpy strcpy
#define mlc_strlen strlen
#define mlc_memcpy memcpy

#endif


#ifdef MLC_MAKE_WINDOWS_THREAD_SAFE

#include <windows.h>

long mlc_thread_lock;

#define SPIN_AND_GET_ACCESS \
{\
 for(;;)\
 {\
  if(InterlockedExchange(&mlc_thread_lock,1)!=0)\
   /* prior value for mlc_thread_lock was 1 so another thread*/\
   /*still has access*/\
   Sleep(0);/*sleep for the rest of this time slice - efficient busy wait\
  else\
   /*prior value was 0 and it is now set to 1 to*/\
   /*indicate that this thread has access*/\
   /*Now everything we do is atomic*/\
   break;\
 }\
}

#define CEDE_ACCESS  {InterlockedExchange(&mlc_thread_lock, 0);}

#else
#define SPIN_AND_GET_ACCESS
#define CEDE_ACCESS
#endif


void DefaultMemLeakWarnProc(char *szmsg)
{
 fprintf(stderr,"%s",szmsg);
}

void (__cdecl *MemLeakWarnProc)(char *szmsg)=DefaultMemLeakWarnProc;

MEMLEAK_CHECK_WM_DLL_A void MEMLEAK_CHECK_WM_DLL_B SetMemLeakWarnProc(void (__cdecl *NewMemLeakWarnProc)(char *szmsg))
{
 MemLeakWarnProc=NewMemLeakWarnProc;
}

void (__cdecl *MemLeakLogProc)(char *szmsg)=NULL;

MEMLEAK_CHECK_WM_DLL_A void MEMLEAK_CHECK_WM_DLL_B SetMemLeakLogProc(void (__cdecl *NewMemLeakLogProc)(char *szmsg))
{
 // This is called if one has a potentially long list of messages
 // to show - an appropriate implementation in a Windows program
 // would be to log them to disk
 MemLeakLogProc=NewMemLeakLogProc;
}


void DefaultMemLeakAnticipateFreeProc(char *szFile,int nLine)
{
 int n=nLine;// just to remove the level 4 warning errors if they are
 // unreferenced
 char *sz=szFile;
 sz=sz;
 n=n;
 /** This is one way to do it if you need this feature: 
 {
  FILE *fp=fopen("most _recent_mem_free","w");
  if(fp)
  {
   fprintf(fp,"Most Recent Free at: File: %s, Line %d",szFile,nLine);
   fclose(fp);
  }
 }
 **/
}

void (__cdecl *MemLeakAnticipateFreeProc)(char *szFile,int nLine)=DefaultMemLeakAnticipateFreeProc;

MEMLEAK_CHECK_WM_DLL_A void MEMLEAK_CHECK_WM_DLL_B SetMemLeakAnticipateFreeProc(void( __cdecl *NewMemLeakAnticipateFreeProc)(char *szFile,int nLine))
{
 MemLeakAnticipateFreeProc=NewMemLeakAnticipateFreeProc;
}

#ifdef cl_mem_leak_check

#ifdef cl_mem_leak_diagnostics
 #ifdef cl_qsort_pointers
int CompareMallocInfo(const MallocInfo * *ppmi1
                            , const MallocInfo * *ppmi2)
{
 if(ppmi1==NULL||*ppmi1==NULL) return 2;
 if(ppmi2==NULL||*ppmi2==NULL) return 1;
 if((*ppmi1)->szFile[0]!=(*ppmi2)->szFile[0])
  return (*ppmi1)->szFile[0]-(*ppmi2)->szFile[0];
 if(strcmpi((*ppmi1)->szFile,(*ppmi2)->szFile))
  return strcmpi((*ppmi1)->szFile,(*ppmi2)->szFile);
 if((*ppmi1)->iLine!=(*ppmi2)->iLine)
  return (*ppmi1)->iLine-(*ppmi2)->iLine;
 return (*ppmi1)->size-(*ppmi2)->size;
}
 #endif
#endif

MEMLEAK_CHECK_WM_DLL_A int MEMLEAK_CHECK_WM_DLL_B MemLeakCheckAndFree(void)
{
 // returns number of pointers freed, or 0 if no leak is found
 int i=0;
 int npointers_freed=0;
#ifdef cl_mem_leak_diagnostics
 char info_line_shown=0;
 char *szFile=NULL;
 long lmem_leak=0;
 long lmem_leak_debug_info=0;
 int iLine=-1,size=-1,nidentical=0;
 void (__cdecl *LocalMemLeakLogProc)(char *szmsg)=MemLeakLogProc?MemLeakLogProc:MemLeakWarnProc;
#endif
 SPIN_AND_GET_ACCESS
 if(!pointers_allocated)
  return 0;
#ifdef cl_mem_leak_diagnostics
 #ifdef cl_qsort_pointers
 // Sort the pointer array according to file, line number then size
 qsort (&pointers_allocated[0], npointers_allocated,sizeof(MallocInfo *),CompareMallocInfo) ;
 #endif
#endif
 for(i=0;i<npointers_allocated;i++)
  if(pointers_allocated[i])
  {
#ifdef cl_mem_leak_diagnostics
   MallocInfo *pmi=(MallocInfo *)pointers_allocated[i];
   char szmsg[10240];
   sprintf(szmsg,"File: %s, line %d, size %d\n"
          ,pmi->szFile,pmi->iLine,pmi->size
          );
   lmem_leak_debug_info+=MALLOC_INFO_SIZE;
   lmem_leak+=pmi->size;
   if(!info_line_shown)
    LocalMemLeakLogProc("Memory Leak!\n");
   info_line_shown=1;
   if(szFile==pmi->szFile&&iLine==pmi->iLine&&size==pmi->size)
    nidentical++;
   else
   {
    if(nidentical)
    {
     char szmsg2[100];
     sprintf(szmsg2,"Another %d pointers - same line, and size\n"
            ,nidentical
            );
     LocalMemLeakLogProc(szmsg2);
    }
    nidentical=0;
    LocalMemLeakLogProc(szmsg);
    szFile=(char *)pmi->szFile;
    iLine=pmi->iLine;
    size=pmi->size;
   }
#endif
   mlc_free(pointers_allocated[i]);
   pointers_allocated[i]=NULL;
   npointers_freed++;
  }
#ifdef cl_mem_leak_diagnostics
  if(nidentical)
  {
   char szmsg2[100];
   sprintf(szmsg2,"Another %d pointers allocated - same line, and same size\n"
          ,nidentical
          );
   LocalMemLeakLogProc(szmsg2);
  }
  if(info_line_shown)
  {
   char szmsg[1024];
   lmem_leak_debug_info+=max_pointers_allocated*sizeof(void *);
   sprintf
    (szmsg,"Memory leak detected!:\n"
    "%d pointers, %d bytes (%.2f Kb)\n%s"
    "Extra memory for debug info: %d bytes (%.2f Kb)\nAverage per pointer %g bytes\n\n"
    "Total allocations in session including ones freed normally:\n"
    "%d pointers, %.0f bytes (%.2f Kb)\n" 
    ,npointers_freed,lmem_leak,(float)lmem_leak/1024.0
    ,LocalMemLeakLogProc==MemLeakWarnProc?"":"See the memory leak log for details\n\n"
    ,lmem_leak_debug_info,(float)lmem_leak_debug_info/1024.0,(float)lmem_leak_debug_info/npointers_freed
    ,ntotal_allocations,dmem_alloc,dmem_alloc/1024.0
    );
   MemLeakWarnProc(szmsg);
  }
#endif
 npointers_allocated=max_pointers_allocated=0;
 mlc_free(pointers_allocated);
 pointers_allocated=NULL;
 CEDE_ACCESS
 return npointers_freed;
}

void CompactifyPointersArray(void)
{
 int i=0;
 int ipos=0;
#ifdef DEBUG_P
 return;
#endif
 for(i=0;i<npointers_allocated;i++)
 {
  if(pointers_allocated[i])
  {
   if(ipos==i)
    ipos++;
   else
   {
    mlc_memcpy(pointers_allocated[i],&ipos,sizeof(int));
     // works for cl_mem_leak_diagnostics too, 
    // because nrecord is the first element of the struct
    pointers_allocated[ipos++]=pointers_allocated[i];
   }
  }
 }
 npointers_allocated=ipos;
 ntest_compactify_pointers_at=ntotal_allocations+max(1024,npointers_allocated/2);
}

#ifdef cl_mem_leak_diagnostics
void AddPointerAllocated(int new_record_number,void *pinfo,size_t size,const char *szFile,int iLine)
#else
void AddPointerAllocated(int new_record_number,void *pinfo,size_t size)
#endif
{
 if(!pinfo)
  return;
 SPIN_AND_GET_ACCESS
 {
  int record_number=new_record_number>=0?new_record_number:npointers_allocated;
  record_number=min(record_number,npointers_allocated);
  if(record_number==npointers_allocated)
  {
   if(npointers_allocated+1>max_pointers_allocated)
   {
    int new_max_pointers_allocated=max_pointers_allocated+POINTER_ALLOC_INCR;
    MallocInfo **new_pointers_allocated=(MallocInfo**)mlc_realloc((void *)pointers_allocated,new_max_pointers_allocated*sizeof(void *));
    if(new_pointers_allocated)
    {
     pointers_allocated=new_pointers_allocated;
     max_pointers_allocated=new_max_pointers_allocated;
    }
    else
     return;
   }
  }
  if(record_number<npointers_allocated)
  if(pointers_allocated[record_number])
   record_number=record_number;// shouldn't happen
 #ifdef cl_mem_leak_diagnostics
  {
   MallocInfo mi;
   mi.nrecord=record_number;
   mi.szFile=szFile;
   mi.iLine=iLine;
   mi.size=size;
   mlc_memcpy(pinfo,&mi,sizeof(MallocInfo));
  #ifdef _DEBUG 
   was_MI=mi;
  #endif
  }
 #else
  mlc_memcpy(pinfo,&record_number,sizeof(int));
 #endif
  pointers_allocated[record_number]=pinfo;
  if(record_number==npointers_allocated)
   npointers_allocated++;
  ntotal_allocations++;
  dmem_alloc+=size;
  if(ntotal_allocations>=ntest_compactify_pointers_at)
  {
   CompactifyPointersArray();
  }
 }
 CEDE_ACCESS
}

#ifdef cl_mem_leak_diagnostics
char RemovePointerAllocated(void *pinfo,char *szFile,int nLine)
#else
char RemovePointerAllocated(void *pinfo)
#endif
{
 int i=0;
 if(!pinfo)
 {
  char szmsg[1024];
  sprintf
   (szmsg,"Attempt to free Null pointer - ignored.\n"
#ifdef cl_mem_leak_diagnostics
   "At: %s: Line %d\n"
   ,szFile,nLine
#endif
   );
  MemLeakWarnProc(szmsg);
  return 0;
 }
#ifdef cl_mem_leak_diagnostics
 MemLeakAnticipateFreeProc(szFile,nLine);
 #ifdef _DEBUG 
  mlc_memcpy(&was_MI,pinfo,sizeof(MallocInfo));
 #endif
#endif
 mlc_memcpy(&i,pinfo,sizeof(int));
 SPIN_AND_GET_ACCESS
 if(i>=0&&i<npointers_allocated&&pointers_allocated[i]==pinfo)
 {
  pointers_allocated[i]=NULL;
#ifndef DEBUG_P
  if(i==npointers_allocated-1)
  {

   for(;i>=0;i--)
   if(pointers_allocated[i])
   {npointers_allocated=i+1;break;}
   
  }
#endif
  CEDE_ACCESS
  return 1;
 }
 CEDE_ACCESS
#ifdef MLC_WARN_ON_ATTEMPT_TO_FREE_WILD_POINTER
 {
  char szmsg[1024];
  sprintf
   (szmsg,"Wild pointer!\n"
   "Not allocated, or freed already\n"
   "Attempt made to free it - ignored.\n"
#ifdef cl_mem_leak_diagnostics
   "At: %s: Line %d\n"
   ,szFile,nLine
#endif
   );
  MemLeakWarnProc(szmsg);
 }
#endif
 return 0;
}

#ifdef cl_mem_leak_diagnostics
MEMLEAK_CHECK_WM_DLL_A void * MEMLEAK_CHECK_WM_DLL_B xz_realloc(void *p,size_t size,char *szFile,int iLine)
#else
MEMLEAK_CHECK_WM_DLL_A void * MEMLEAK_CHECK_WM_DLL_B xz_realloc(void *p,size_t size)
#endif
{
 void *pret=NULL;
 void *pinfo=p==NULL?NULL:((byte *)p-MALLOC_INFO_SIZE);
 MallocInfo *pmi=(MallocInfo *)pinfo;
#ifdef cl_mem_leak_diagnostics
 int i=pmi?pmi->nrecord:-1;
 #ifdef _DEBUG
 was_MI=pmi?*pmi:was_MI;
 #endif
#else
 int i=pmi?(int)*((int *)pmi):-1;
#endif
 {
  void *pnew=mlc_realloc(pinfo,size+MALLOC_INFO_SIZE);
  if(pnew)
  {
   if(pmi)
   {
    // Note - pmi is no longer a valid pointer, so we can't use RemovePointerAllocated(..)
    // so remove it here using the record number obtained before it was reallocated
    SPIN_AND_GET_ACCESS
    if(i>=0&&i<npointers_allocated)
    {
     if(pointers_allocated[i]==pinfo)
      pointers_allocated[i]=NULL;
    }
    CEDE_ACCESS
   }
#ifdef cl_mem_leak_diagnostics
   AddPointerAllocated(i,pnew,size,szFile,iLine);
#else
   AddPointerAllocated(i,pnew,size);
#endif
   pret=(void *)((byte *)pnew+MALLOC_INFO_SIZE);
  }
 }
 return pret;
}


#ifdef cl_mem_leak_diagnostics
MEMLEAK_CHECK_WM_DLL_A void * MEMLEAK_CHECK_WM_DLL_B xz_malloc(size_t size,char *szFile,int iLine)
#else
MEMLEAK_CHECK_WM_DLL_A void * MEMLEAK_CHECK_WM_DLL_B xz_malloc(size_t size)
#endif
{
 void *pinfo=mlc_malloc(size+MALLOC_INFO_SIZE);
 void *pret=NULL;
 if(pinfo)
 {
#ifdef cl_mem_leak_diagnostics
  AddPointerAllocated(-1,pinfo,size,szFile,iLine);
#else
  AddPointerAllocated(-1,pinfo,size);
#endif
  pret=(byte *)pinfo+MALLOC_INFO_SIZE;
 }
 return pret;
}

#ifdef cl_mem_leak_diagnostics
MEMLEAK_CHECK_WM_DLL_A void * MEMLEAK_CHECK_WM_DLL_B xz_calloc(size_t num,size_t size,char *szFile,int iLine)
#else
MEMLEAK_CHECK_WM_DLL_A void * MEMLEAK_CHECK_WM_DLL_B xz_calloc(size_t num,size_t size)
#endif
{
 void *pinfo=mlc_calloc(1,size*num+MALLOC_INFO_SIZE);
 void *pret=NULL;
 if(pinfo)
 {
#ifdef cl_mem_leak_diagnostics
  AddPointerAllocated(-1,pinfo,size,szFile,iLine);
#else
  AddPointerAllocated(-1,pinfo,size);
#endif
  pret=(byte *)pinfo+MALLOC_INFO_SIZE;
 }
 return pret;
}

#ifdef cl_mem_leak_diagnostics
MEMLEAK_CHECK_WM_DLL_A void MEMLEAK_CHECK_WM_DLL_B xz_free(void *p,char *szFile,int iLine)
#else
MEMLEAK_CHECK_WM_DLL_A void MEMLEAK_CHECK_WM_DLL_B xz_free(void *p)
#endif
{
 if(p)
 {
  byte *pinfo=((byte *)p-MALLOC_INFO_SIZE);
#ifdef cl_mem_leak_diagnostics
  if(RemovePointerAllocated(pinfo,szFile,iLine))
#else
  if(RemovePointerAllocated(pinfo))
#endif
  mlc_free(pinfo);
 }
}

#ifdef cl_mem_leak_diagnostics
MEMLEAK_CHECK_WM_DLL_A char * MEMLEAK_CHECK_WM_DLL_B xz_strdup(const char *src,char *szFile,int iLine)
#else
MEMLEAK_CHECK_WM_DLL_A char * MEMLEAK_CHECK_WM_DLL_B xz_strdup(const char *src)
#endif
{\
 int len= mlc_strlen(src);
#ifdef cl_mem_leak_diagnostics
 byte *dest=xz_malloc(len+1,szFile,iLine);
#else
 byte *dest=xz_malloc(len+1);
#endif
 if(dest)
  mlc_strcpy((char *)dest,src);\
 return (char *)dest;
}

#endif

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United Kingdom United Kingdom
Trained as a mathematician.

Now I program shareware apps - music and 3D. Main ones so far, Fractal Tune Smithy (music) and Virtual Flower (3D / VRML).

Comments and Discussions