Free Memory Leak Checker (C++)

Started by
13 comments, last by TechnoCore 16 years, 2 months ago
I've been meaning to tidy this code up for a while and then post it here, but I just never got around to it. Then, today MindWipe was having issues with the CRT memory leak code, so I decided to get of my ass and tidy it up and hand it over. The code was originally in my engine, and I've just removed the engine specific stuff really. So, here is MemMgr.h and MemMgr.cpp. It's a couple of files that you can just drop into any project, rebuild all, and you'll get free memory leak checking code and a few other bits and bobs. You don't need to #include "MemMgr.h" or anything. Example usage:

int main(int, char**)
{
   int* n = new int[42];
   return 0;
}




That'll give you debug spew at application shutdown, and write to memleaks.log, then debug break. The debug spew is the same as you get in memleaks.log, and is (In this case):

1 active allocations totalling 168 bytes:
+ ID 00000002, tag ID 0x00000000: 0x00356B84 168 bytes [main.cpp:9 (main)]
End of allocations




The code only works on Windows, and should work fine on x64. It'll also only work on Visual Studio, due to my #pragma black magic. Only tested on VC2005 so far, but it should work fine in VC2003 or VC2008. A few bits and bobs:
  • It uses a singleton for the memory manager (I know, I'm sorry to all the singleton haters out there), but I believe it's perfectly justified in this case.
  • It uses some #pragma black magic to make sure the memory manager is one of the very first things created, and one of the very last things destroyed. That means it can correctly track STL memory leaks (Although you should only get them if you have e.g. a std::vector in a class allocated with new). This does mean that it might not play nicely in some cases. Please let me know if you find such a case.
  • It does some stack walking with the StackWalk64() function to determine where the allocations came from when memory leaks are detected. It won't give you a full call stack, if you want that it should be straightforwards to modify the code. Instead, it just gives the first function on the stack that isn't in the memory manager, operator new, or STL code.
  • It's thread safe.
  • It only tracks allocations made with new or new[]. It won't handle malloc() allocations (Again, it could be modified to do that I suppose).
  • There's some statistics code you can access if you #include "MemMgr.h" and then access the various functions, e.g. MemMgr::Get().GetAllocatedBytes().
  • The memory manager only compiles into a debug build. Release builds will use the normal operators new and delete. You could change this if you like, but I wouldn't advise it. I'm not sure if this topic should go in Your Announcements, but I figured more people would read it here, and it's a very programming related thing. If any of the mods disagree, feel free to move the topic. If anyone has any questions or problems with it, feel free to post here or PM me. If someone could give it a whirl on VC2003 or VC2008 too, that would be good. EDIT: Updated the code to do runtime linking to RtlCaptureContext; apparently the Platform SDK that comes with VC2008 doesn't know about it. Cheers, Steve [Edited by - Evil Steve on February 14, 2008 8:53:42 AM]
  • Advertisement
    Quote:It uses a singleton for the memory manager (I know, I'm sorry to all the singleton haters out there), but I belive it's perfectly justified in this case.


    Fortunately for you, it's not a kosher singleton, since it uses explicit and not lazy construction.

    So I guess you're off the hook, and the class is merely a global. Still, at quick glance, you might as well replace the class with namespace, and not lose anything at all.


    Quote:It uses some #pragma black magic to make sure the memory manager is one of the very first things created,


    This intrigues me. At least I've never heard of such technique, most other memory managers need to be included at beginning.
    Quote:Original post by Antheus
    Fortunately for you, it's not a kosher singleton, since it uses explicit and not lazy construction.

    So I guess you're off the hook, and the class is merely a global. Still, at quick glance, you might as well replace the class with namespace, and not lose anything at all.
    That's a good point, I'll add that to my TODO list for it.


    Quote:Original post by Antheus
    Quote:It uses some #pragma black magic to make sure the memory manager is one of the very first things created,
    This intrigues me. At least I've never heard of such technique, most other memory managers need to be included at beginning.
    Technically, it makes a global in the init_seg section. That global should be the first constructed and the last destructed (If any other globals are in the init_seg the order is undefined). The global creates and destroys the memory manager; the important part is that the memory manager is destroyed after everything else.
    I have a very similar memory tracker and is based of C++ for game programmers and it looks like your code is also a derivative of it. I thought that Noel Llopis' code had a license? If not it, it would be nice to just give some credit to the original author.
    Quote:Original post by dmail
    I have a very similar memory tracker and is based of C++ for game programmers and it looks like your code is also a derivative of it. I thought that Noel Llopis' code had a license? If not it, it would be nice to just give some credit to the original author.
    That's the first I've seen of that actually. In what ways is it similar? A lot of memory leak checkers are based on similar principals.
    Quote:Original post by Antheus
    Quote:It uses a singleton for the memory manager (I know, I'm sorry to all the singleton haters out there), but I belive it's perfectly justified in this case.


    Fortunately for you, it's not a kosher singleton, since it uses explicit and not lazy construction.

    So I guess you're off the hook, and the class is merely a global. Still, at quick glance, you might as well replace the class with namespace, and not lose anything at all.


    What does 'Kosher Singleton' mean, and why is it bad?

    /Curious

    //TechnoCore
    Quote:Original post by TechnoCore
    What does 'Kosher Singleton' mean, and why is it bad?

    /Curious
    "Kosher" meaning "normal" or "standard" singleton. A normal singleton constructs itself the first time it's referenced, E.g.:
    MemMgr& MemMgr::Get(){   static MemMgr instance;   return instance;}
    Or similarly, with dynamic allocation. In this case, the singleton is created and destroyed explicitly - the key point is that it's not constructed the first time it's used.

    Singletons are usually considered a sign of bad design, since they're really just globals in disguise. See Promit's Journal for some relevant links.
    Nice that you release code :)
    Some comments, since I am working on the same thing ATM:
    - __declspec(allocate) may be preferable to #pragma init_seg in that it avoids C4074
    - using an allocation hook instead of overloading new is a bit more robust (what happens if you link against another library that decides to do the same?) and also covers use of malloc/getcwd/strdup etc.

    More importantly: we certainly can't hope to reach Valgrind's degree of studliness, so it makes sense to use that on Linux and otherwise consider the MS and Mac platform libraries. The MS debug heap already does all of this and more - except for recording a call stack. Wouldn't it be nifty to reuse that code, just modifying it to also gather a stack trace? This is what the earlier, safer versions of VLD did.

    In fact, we can take this approach one step further. Storing 64 bytes of caller information *for each allocation* is a bit hard to justify, especially if there are lots of allocations. (what good is debug mode if you can't run it or it's just too slow?) It turns out that several frames can be stored with *zero* space overhead: we just need to stash them in the memory block header's file and line fields. Those 64 bits (for 32-bit builds) are enough to store at least two full 32-bit pointers* or up to six encoded offsets, the first being relative to the code segment.

    * dirty trick: you need one bit to differentiate the pointer vs offset, but get it back by not storing the LSB (since no call instruction can be less than two bytes, rounding the return address down to the next even address does not change the function).


    > today MindWipe was having issues with the CRT memory leak code
    Out of curiosity, what kind of problems?
    E8 17 00 42 CE DC D2 DC E4 EA C4 40 CA DA C2 D8 CC 40 CA D0 E8 40E0 CA CA 96 5B B0 16 50 D7 D4 02 B2 02 86 E2 CD 21 58 48 79 F2 C3
    Quote:Original post by Jan Wassenberg
    Nice that you release code :)
    Some comments, since I am working on the same thing ATM:
    - __declspec(allocate) may be preferable to #pragma init_seg in that it avoids C4074
    - using an allocation hook instead of overloading new is a bit more robust (what happens if you link against another library that decides to do the same?) and also covers use of malloc/getcwd/strdup etc.

    More importantly: we certainly can't hope to reach Valgrind's degree of studliness, so it makes sense to use that on Linux and otherwise consider the MS and Mac platform libraries. The MS debug heap already does all of this and more - except for recording a call stack. Wouldn't it be nifty to reuse that code, just modifying it to also gather a stack trace? This is what the earlier, safer versions of VLD did.

    In fact, we can take this approach one step further. Storing 64 bytes of caller information *for each allocation* is a bit hard to justify, especially if there are lots of allocations. (what good is debug mode if you can't run it or it's just too slow?) It turns out that several frames can be stored with *zero* space overhead: we just need to stash them in the memory block header's file and line fields. Those 64 bits (for 32-bit builds) are enough to store at least two full 32-bit pointers* or up to six encoded offsets, the first being relative to the code segment.

    * dirty trick: you need one bit to differentiate the pointer vs offset, but get it back by not storing the LSB (since no call instruction can be less than two bytes, rounding the return address down to the next even address does not change the function).


    > today MindWipe was having issues with the CRT memory leak code
    Out of curiosity, what kind of problems?
    Interesting, I didn't know about allocation hooks or __declspec(allocate), I'll have a look at them. I agree that storing 64 bytes (Actually, it's more with the stack trace contexts [rolleyes]) is a bit much for each allocation, but I haven't run into any problems with it (yet).

    The problem MindWipe was having turned out to be a genuine memory leak (GLUT never returning, so memory never being deleted), but I thought it was because some global was getting destroyed after the _CrtDumpLeakCheck (Or whatever the function is).
    Another thing you can do is store the stack traces separately. This is so that if you have a lot of memory being allocated at a particular point in code with a unique "call stack signature", you don't have to store it on a per-allocation basis, but rather just once in the unique list and have the allocation point to its stack trace. Our engine at work uses a std::hash_map (with a custom allocator of course) to store the stack traces with their CRC as a key. Recursion guards are used to prevent memory allocations from within the memory manager from causing infinite loops, while also allowing us to track the amount of memory in use by the memory manager itself. This isn't something to overlook -- at one point we thought our engine was leaking 28 megabytes, until we discovered all that memory was in use by the leak tracking system :)

    This topic is closed to new replies.

    Advertisement