Memory Leak Detection

Started by
8 comments, last by keethrus 19 years, 8 months ago
I know there are quite a few threads on this, but I wrote something that meets my needs, and I wanted to put it out here for the world to see partially so that people can use it, and partially because I'd like to know if there are any serious problems with this approach. edit: tiny fix

#ifndef __MEMTRACKER_H__
#define __MEMTRACKER_H__

#include <cstdlib>
#include <string>
#include <list>
#include <bitset>
#include <iostream>
#include <iomanip>

#ifdef USE_MEMTRACKER

enum MemState { MS_ARRAY=0, MS_DELETED, MS_INC_DELETED };

class MemBlock
{
    public:
        MemBlock(size_t address, size_t size, const std::string &file, const std::string &func, size_t line, bool array) :
            mAddress(address), mSize(size), mFile(file), mFunc(func), mLine(line)
        {
            mState[MS_ARRAY] = array;
        }

        size_t mAddress;
        size_t mSize;
        std::string mFile;
        std::string mFunc;
        size_t mLine;
        std::bitset<3> mState;

        friend std::ostream& operator<<(std::ostream &os, const MemBlock &block);
};

std::ostream& operator<<(std::ostream &os, const MemBlock &block)
{
    return os << std::setw(9) << block.mSize << " bytes allocated at 0x" << std::ios::hex << block.mAddress 
        << " in " << block.mFunc << " (" << block.mFile << ":" << std::setw(4) << block.mLine << ") "
        << (block.mState[MS_ARRAY]?"Array ":" ")
        << (block.mState[MS_DELETED]?"Deleted ":"LEAKED ")
        << (block.mState[MS_INC_DELETED]?"Incorrectly Deleted ":"");
}

class MemTracker
{
    typedef std::list<MemBlock> BlockList;
    typedef BlockList::iterator BlockIterator;

    private:

        static BlockList sMemBlocks;
        static size_t sCurMem;
        static size_t sMaxMem;

    public:

        static void newBlock(const MemBlock &block)
        {
            sMemBlocks.push_back(block);
            sCurMem += block.mSize;
            sMaxMem = std::max(sCurMem,sMaxMem);
        }

        static void deleteBlock(size_t address, bool arrayDelete)
        {
            for(BlockIterator it=sMemBlocks.begin(); it != sMemBlocks.end(); ++it)
            {
                if(it->mAddress == address)
                {
                    sCurMem -= it->mSize;

                    if(it->mState[MS_ARRAY])
                    {
                        if(arrayDelete)
                            it->mState[MS_DELETED] = true;
                        else
                            it->mState[MS_INC_DELETED] = true;
                    }
                    else
                    {
                        if(!arrayDelete)
                            it->mState[MS_DELETED] = true;
                        else
                            it->mState[MS_INC_DELETED] = true;
                    }
                }
            }
        }

        static void showAllMem(std::ostream &os)
        {
            os << "--Memory Allocated--" << std::endl;
            for(BlockIterator it=sMemBlocks.begin(); it != sMemBlocks.end(); ++it)
                os << *it << std::endl;
        }

        static bool leaksExist()
        {
            for(BlockIterator it=sMemBlocks.begin(); it != sMemBlocks.end(); ++it)
            {
                if(!it->mState[MS_DELETED])
                    return true;
            }
            return false;
        }

        static void showLeaks(std::ostream &os)
        {
            os << "--Memory Leaked--" << std::endl;
            for(BlockIterator it=sMemBlocks.begin(); it != sMemBlocks.end(); ++it)
            {
                if(!it->mState[MS_DELETED])
                    os << *it << std::endl;
            }
        }

        static void showVitals(std::ostream &os)
        {
            os << "--Memory Report--" << std::endl
                << "Currently Allocated: " << std::setw(9) << sCurMem << " bytes" << std::endl
                << "Maximum Allocated:   " << std::setw(9) << sMaxMem << " bytes" << std::endl;
        }
};

MemTracker::BlockList MemTracker::sMemBlocks;
size_t MemTracker::sCurMem;
size_t MemTracker::sMaxMem;

inline void* operator new(size_t size, const char* file, const char* func, int line)
{
    void *ptr = std::malloc(size);
    MemTracker::newBlock(MemBlock((size_t)ptr, size, file, func, line, false));
    return ptr;
}

inline void* operator new[](size_t size, const char* file, const char* func, int line)
{
    void *ptr = std::malloc(size);
    MemTracker::newBlock(MemBlock((size_t)ptr, size, file, func, line, true));
    return ptr;
}

inline void operator delete(void* ptr)
{
    MemTracker::deleteBlock((size_t)ptr, false);
    std::free(ptr);
}

inline void operator delete[](void* ptr)
{
    MemTracker::deleteBlock((size_t)ptr, true);
    std::free(ptr);
}

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

#else //USE_MEMTRACKER

class MemTracker
{
    public:
        static void showAllMem(std::ostream &os) {}
        static void showLeaks(std::ostream &os) {}
        static void showVitals(std::ostream &os) {}
        static bool leaksExist() { return false; }
};

#endif //USE_MEMTRACKER

#endif //__MEMTRACKER_H__




I meant to add a flag for new/new[] to make sure that the proper delete/delete[] works. Oh and if you're looking to test this:

#define USE_MEMTRACKER
#include "MemTracker.h"
#include <iostream>

int main()
{
    int *a = new int;
    int *b = new int[40];
    int *c = new int;
    int *d = new int[40];
    int *e = new int;
    int *f = new int[40];
    delete a;
    delete b;
    delete []c;
    delete []d;

    MemTracker::showVitals(std::cout);
    std::cout << std::endl;
    MemTracker::showAllMem(std::cout);
    std::cout << std::endl;
    if(MemTracker::leaksExist())
        MemTracker::showLeaks(std::cout);
}







You should get: --Memory Report-- Currently Allocated: 164 bytes Maximum Allocated: 492 bytes --Memory Allocated-- 4 bytes allocated at 0x84014000 in main (new.cpp: 6) Deleted 160 bytes allocated at 0x84008312 in main (new.cpp: 7) Array LEAKED Incorrectly Deleted 4 bytes allocated at 0x84014032 in main (new.cpp: 8) LEAKED Incorrectly Deleted 160 bytes allocated at 0x84008480 in main (new.cpp: 9) Array Deleted 4 bytes allocated at 0x84014048 in main (new.cpp: 10) LEAKED 160 bytes allocated at 0x84008648 in main (new.cpp: 11) Array LEAKED --Memory Leaked-- 160 bytes allocated at 0x84008312 in main (new.cpp: 7) Array LEAKED Incorrectly Deleted 4 bytes allocated at 0x84014032 in main (new.cpp: 8) LEAKED Incorrectly Deleted 4 bytes allocated at 0x84014048 in main (new.cpp: 10) LEAKED 160 bytes allocated at 0x84008648 in main (new.cpp: 11) Array LEAKED [Edited by - cozman on August 6, 2004 2:33:42 AM]
Advertisement
Interesting. Might be a good idea to just run-time link these alternate new/delete operators, such that you can run any program, without change or recompilation, and track its memory use. Of course, this might be exactly how valgrind (1) works, as this is what it accomplishes.

alternatively, use Python (2)

(1) http://valgrind.kde.org/
(2) http://www.python.org/
That's a very interesting suggestion, but I'd have no idea how to go about doing that, and it'd require several substantial changes, the __FUNCTION__/__LINE__/__FILE__ would not be available.
It looks great. But you might want to think about making another type of report, which only shows allocated memory that was not deleted. Once your project gets large, you'll never want to look through the big list of allocations, and you'll never see the [LEAK]'s.

I have entire lines of '!' surrounding my leaks in my runtime log, and I still miss them sometimes :)
Very nice codesnippet! You should submit it somewhere.. CodeProject? GameDev.net?

Quote:Original post by Jiia
It looks great. But you might want to think about making another type of report, which only shows allocated memory that was not deleted. Once your project gets large, you'll never want to look through the big list of allocations, and you'll never see the [LEAK]'s.

I have entire lines of '!' surrounding my leaks in my runtime log, and I still miss them sometimes :)

You can easily add that functionality yourself, with his clean coding.. :)

[minor thing] The #else is incorrectly commented. It says '// USE MEMTRACKER' while it should be '//DON'T USE MEMTRACKER'.
Quote:Original post by Jiia
It looks great. But you might want to think about making another type of report, which only shows allocated memory that was not deleted. Once your project gets large, you'll never want to look through the big list of allocations, and you'll never see the [LEAK]'s.

I have entire lines of '!' surrounding my leaks in my runtime log, and I still miss them sometimes :)


There are a few problems I can see arising with this, and I'll explain a bit on my memory tracker to hopefully shed a bit of light :).

Mine does this: Each time a new entry is added, it opens the file and writes it (add or delete) and closes the file again. This ensures that if a crash happens, I can see what the last successful memory allocation was. Then at the end, in my destructor, is where I print out my list of remaining memory addresses (these are leaks), so when looking at my report I first have my list of everything, then a list of just the stuff never deleted, makes it much simpler for viewing. I also track some other crap just for the heck of it, like the maximum amount of memory allocated at any one time, just to see how much memory allocating i'm actually doing.
Works pretty well, just gave it a small test now... ill do some proper scrutinizing later when I get time but well done!
-----------------------------------------------------What do you call people who are afraid of Santa Claus? Claustrophobic!
Quote:Original post by Ready4Dis
Mine does this: Each time a new entry is added, it opens the file and writes it (add or delete) and closes the file again. This ensures that if a crash happens, I can see what the last successful memory allocation was. Then at the end, in my destructor, is where I print out my list of remaining memory addresses (these are leaks), so when looking at my report I first have my list of everything, then a list of just the stuff never deleted, makes it much simpler for viewing. I also track some other crap just for the heck of it, like the maximum amount of memory allocated at any one time, just to see how much memory allocating i'm actually doing.

Wow, same methods I use. Well, for my runtime log. My memory routines only show the amount of memory allocated at once and all memory allocated total, at shutdown, if there are no leaks. And it won't print that info if there is a crash :)

One strange thing I've noticed is that my total memory allocations size is always larger than what the Windows XP task manager (processes tab) shows. But this may just be because I've tabbed out of my game to view it.

But again, very nice work!
I made some changes based on suggestions I recieved here and the idea to make sure that new[] had a matching delete[]. I went ahead and updated the code on the top and the example code.
Quote:Original post by Ready4Dis
Mine does this: Each time a new entry is added, it opens the file and writes it (add or delete) and closes the file again. This ensures that if a crash happens, I can see what the last successful memory allocation was. Then at the end, in my destructor, is where I print out my list of remaining memory addresses (these are leaks), so when looking at my report I first have my list of everything, then a list of just the stuff never deleted, makes it much simpler for viewing. I also track some other crap just for the heck of it, like the maximum amount of memory allocated at any one time, just to see how much memory allocating i'm actually doing.


Instead of opening, writing, then closing each time -- What I do is just send a "fflush( *fp );", which makes sure everything you have already written gets put to disk. So even in case of a crash, everything will still be in the file.

- Jeremiah

This topic is closed to new replies.

Advertisement