Reputation: 4939
My application is a dll based implementation and probably leaks memory during unload. I have noticed it during unload/reload cycles (when hosting process is not killed). the hosting process's virtual memory is increasing.
i have went through a code inspection to try and find the leaking code but didn't find any.
i'm looking for other techniques to detect memory leaks during unload (where objects are being destroyed).
EDIT: I am using win32 (XP) platform.
do you have experience in such tools/procedures? Thanks
Upvotes: 2
Views: 1163
Reputation: 4543
you didn't specify which platform you are looking for. I'm a windows developer so I can only recommend windows solutions. If that's what you are working with, there's a number of commercial and free tools available. Few that I personally worked with: Purify, BoundsChecker, UMDH, LeakDiag, DebugDiag.
Out of these, I typically prefer UMDH. It is free and comes as part of Debugging Tools for Windows (DTW) installation. I found it to actually be more reliable and less resource intensive than most other, including professional tools. It is very simply to use and documentation can be found in a .chm file which comes with DTW install. In the end, I personally find that UMDH has very high signal to noise ratio compared to many other tools.
DebugDiag is another good alternative. As far as I can tell, it uses almost same APIs as UMDH but is slightly more cumbersome to use because it's UI-based rather than command prompt so to get things done usually requires more clicks, but for someone new, I would recommend it over UMDH.
UPDATE:
It's interesting that majority preference was to insert custom hooks into malloc/free and then add even more code for custom hooks into operators new/delete.
I would strongly suggest you take a look at UMDH and learn how it works even if you don't find in necessary in this specific case. At the core of all memory allocations are windows functions, HeapAlloc/HeapFree. Microsoft, anticipating the need for leak detection methods, already provided hooks that we can use at that root level.
These are other advantages of using UMDH over custom allocator hooks:
Pretty much, any time there's an upward trend in memory usage, the tool will tell you where it's coming from. Most of the time, I can find a leak within 10 minutes if its reproducible on the development machine (it takes a bit longer when debugging production code because symbol files must be matched, sometimes manually).
So if you get all this completely free with DTW installations (which btw has other awesome debugging features), why do people prefer to roll their own leak detection code?
Upvotes: 2
Reputation: 62045
What I used to do a long time ago is this:
I wrote my own mymalloc
, myrealloc
and myfree
(and overloaded new
and delete
so that they would call my functions.) Then I wrote malloc
and realloc
macros which invoked mymalloc
and myrealloc
, passing them __FILE__
and __LINE__
. What mymalloc
did is this: it invoked the malloc
function of the standard library, allocating a slightly larger block, and it inserted __FILE__
and __LINE__
in that block. It also kept all allocated blocks in a linked list, so as to be able to traverse them later.
Upon program exit, I would traverse the list of blocks that had not been freed, and I would print out the file and line that were responsible for the memory leaks.
Nowadays I would assume that there would be ready-made tools that you could get to do that kind of stuff for you.
Upvotes: 4
Reputation: 21541
In addition to Mike's answer (overriding Malloc) you can also overide the new and delete operators in Visual Studio.
Disclaimer: I found this code on the net back in 2004 and included it in a C++ project. I don't know the original source
Below is a code sample (which I included as a header file, memleak.h). This is pretty old code so it may have errors compiling! It does illustrate however how to override new and delete. It also dumps unfreed memory to a file. This code becomes effective if the define _DEBUG is included in your code.
Best regards,
#include <iostream>
#include <list>
using namespace std;
//void DumpUnfreed();
//void AddTrack(DWORD addr, DWORD asize, const char *fname, DWORD lnum);
//void RemoveTrack(DWORD addr);
typedef struct
{
DWORD address;
DWORD size;
char file[64];
DWORD line;
} ALLOC_INFO;
typedef list<ALLOC_INFO*> AllocList;
AllocList *allocList;
void AddTrack(DWORD addr, DWORD asize, const char *fname, DWORD lnum)
{
ALLOC_INFO *info;
if(!allocList)
{
allocList = new(AllocList);
}
info = new(ALLOC_INFO);
info->address = addr;
strncpy(info->file, fname, 63);
info->line = lnum;
info->size = asize;
allocList->insert(allocList->begin(), info);
};
void RemoveTrack(DWORD addr)
{
AllocList::iterator i;
if(!allocList)
return;
for(i = allocList->begin(); i != allocList->end(); i++)
{
if((*i)->address == addr)
{
allocList->remove((*i));
break;
}
}
};
void DumpUnfreed()
{
AllocList::iterator i;
DWORD totalSize = 0;
char buf[1024];
sprintf(buf, "-----------------------------------------------------------\n");
OutputDebugString(buf);
OutputDebugString("DSP.DLL: Detecting unfreed memory...\n");
if(!allocList)
{
OutputDebugString("No memory allocations were tracked!\n");
return;
}
for(i = allocList->begin(); i != allocList->end(); i++)
{
sprintf(buf, "%-50s:\t\tLINE %d,\t\tADDRESS %d\t%d unfreed\n",
(*i)->file, (*i)->line, (*i)->address, (*i)->size);
OutputDebugString(buf);
totalSize += (*i)->size;
}
sprintf(buf, "-----------------------------------------------------------\n");
OutputDebugString(buf);
sprintf(buf, "DSP.DLL Total Unfreed: %d bytes\n", totalSize);
OutputDebugString(buf);
sprintf(buf, "-----------------------------------------------------------\n");
OutputDebugString(buf);
};
#ifdef _DEBUG
inline void * __cdecl operator new(unsigned int size,
const char *file, int line)
{
void *ptr = (void *)malloc(size);
AddTrack((DWORD)ptr, size, file, line);
return(ptr);
};
inline void __cdecl operator delete(void *p)
{
RemoveTrack((DWORD)p);
free(p);
};
inline void * __cdecl operator new[ ] (unsigned int size, const char *file, int line)
{
void *ptr = (void *)malloc(size);
AddTrack((DWORD)ptr, size, file, line);
return(ptr);
};
inline void __cdecl operator delete[ ] (void *p)
{
RemoveTrack((DWORD)p);
free(p);
};
#endif
#ifdef _DEBUG
#define DEBUG_NEW new(__FILE__, __LINE__)
#else
#define DEBUG_NEW new
#endif
#define new DEBUG_NEW
Upvotes: 1
Reputation: 141850
If you can compile your code on a supported platform, you should certainly give Valgrind's Memcheck tool a try. Read The Valgrind Quick Start Guide to see how.
Here's a quick example...
Source:
$ cat -n leaky.cpp
1 struct leaky
2 {
3 leaky()
4 :bytes(new char[256])
5 {
6 }
7
8 char* bytes;
9 };
10
11 int main()
12 {
13 leaky sieve;
14 return sizeof sieve;
15 }
16
Build:
$ make leaky
g++ -Wall -Wextra -Wshadow -pedantic -Wno-long-long -Wfloat-equal -Wcast-qual -g -I/opt/local/include -Weffc++ -Wall -I /opt/local/include -L/opt/local/lib leaky.cpp -o leaky
Check:
$ valgrind --leak-check=full ./leaky
==85800== Memcheck, a memory error detector
==85800== Copyright (C) 2002-2011, and GNU GPL'd, by Julian Seward et al.
==85800== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyright info
==85800== Command: ./leaky
==85800==
==85800==
==85800== HEAP SUMMARY:
==85800== in use at exit: 2,367 bytes in 33 blocks
==85800== total heap usage: 33 allocs, 0 frees, 2,367 bytes allocated
==85800==
==85800== 256 bytes in 1 blocks are definitely lost in loss record 6 of 9
==85800== at 0xB823: malloc (vg_replace_malloc.c:266)
==85800== by 0x5768D: operator new(unsigned long) (in /usr/lib/libstdc++.6.0.9.dylib)
==85800== by 0x576DA: operator new[](unsigned long) (in /usr/lib/libstdc++.6.0.9.dylib)
==85800== by 0x100000EE7: leaky::leaky() (leaky.cpp:4)
==85800== by 0x100000EB3: main (leaky.cpp:13)
==85800==
==85800== LEAK SUMMARY:
==85800== definitely lost: 256 bytes in 1 blocks
==85800== indirectly lost: 0 bytes in 0 blocks
==85800== possibly lost: 0 bytes in 0 blocks
==85800== still reachable: 2,111 bytes in 32 blocks
==85800== suppressed: 0 bytes in 0 blocks
==85800== Reachable blocks (those to which a pointer was found) are not shown.
==85800== To see them, rerun with: --leak-check=full --show-reachable=yes
==85800==
==85800== For counts of detected and suppressed errors, rerun with: -v
==85800== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 1 from 1)
Upvotes: 1