Sign in to follow this  
Max_Payne

C++ : Overloading Global new/delete

Recommended Posts

Well, yesterday I overloaded the global operators new and delete to track memory leaks. I had the (displeasing) surprise of finding out that if you overload them in one location, they become overloaded *everywhere*, no matter if you include the declaration or not. I decided this was still viable, if I changed my design a bit... However... Today, I did some experiments with G++. I overloaded new and delete, along with new[] and delete[]. I made them print to std::cout when allocations and deallocations are performed, and well, something weird is happening. I created an std::vector<float> object, and pushed a float in it. When I do this, it outputs "allocating". However, when the vector goes out of scope, or even if I explicitly call clear() on it, *nothing* happens. It never outputs "deallocating". The only way it will output deallocating is if I explicitly call resize(0). This is kind of weird. I guess the G++ implementation of the STL must perform some kind of memory polling... But if I can't keep track of when the memory is being released (if at all), how am I possibly going to implement a working memory leak tracker...

Share this post


Link to post
Share on other sites

It would have to deallocate the memory at some point, right?

If you are just trying to catch memory leaks, you don't have to worry about when stuff is allocated/deallocated, just that it is. Just do something that checks that no memory is allocated at the end of the program, and if there is, examine the tracking info (your info that you tagged on in new?) of the allocated blocks.

Alternatively, you could look into the CRT debugging functions.

Share this post


Link to post
Share on other sites
Hm. Well. I guess an option would be to use a specialized new that tags information, and to ignore other allocations and deallocations that refer to untagged blocks...

The thing is, it doesn't seem to be deallocating memory... And well, even though it does overload operator new everywhere, my macro to tag information along with it doesn't get replaced everywhere... But then you apparently can't explicitly call an overloaded delete to tag deletion parameters, which gets very annoying... Oh well...

Share this post


Link to post
Share on other sites
Does std::vector explicitly call delete to deallocate itself?

(note that it might very well use free, which wouldn't be cought by your tests)

Share this post


Link to post
Share on other sites
Regarding the CRT memory functions: I'm not entirely sure, but when you set the _CRTDBG_LEAK_CHECK_DF flag, some memory blocks may be printed at the program end which are freed later, for example the memory allocated by global objects, which is freed in the object's destructor. The problem is that the CRT memory dump function is called prior to the global object's destructor. So they may not be well suited to search for memory leaks, at least in C++.

Share this post


Link to post
Share on other sites

Hm.. sounds like scoping issues. I wish I could help you more, but I've never overloaded a global operator, and I'm not quite sure what's going on.

Oh, and why would you want to track just part of the program? Seems that you would want to track the whole program if you are looking for memory leaks.

Share this post


Link to post
Share on other sites
Quote:
Original post by sphinx23
Regarding the CRT memory functions: I'm not entirely sure, but when you set the _CRTDBG_LEAK_CHECK_DF flag, some memory blocks may be printed at the program end which are freed later, for example the memory allocated by global objects, which is freed in the object's destructor. So they may not be well suited to search for memory leaks, at least in C++.


Actually, that's the point of that flag. Instead of just calling _CrtDumpMemoryLeaks() at the end of your program, you can set that flag to tell it to call the function as the very last step of the program, even after global objects have been deallocated.

At least it worked in my case. I had a couple global objects that would show up as leaks if I printed them at the end of the program, but when setting that flag it said I had no leaks.

Share this post


Link to post
Share on other sites
OT: okonomiyaki: Well, it didn't work in my case ;). The list was printed, an then the destructors were called, which i had verified by putting breakpoints in them. The CRT said there was memory leaked whereas in reality this memory was freed...

Share this post


Link to post
Share on other sites
Okay. So I assume that function to dump memory leaks might work for me. Although I am in a special case (many dynamically linked DLLs). It should still be able to find all that was allocated and deallocated in the process I presume.

But how does it work? What does it do? Does it generate a log file of some sort? And when it reports memory leaks, does it tell you where in the code those things were allocated?

Share this post


Link to post
Share on other sites
MSDN . It uses the debugging output to print the leaks. It also provides some mechanism to track down the memory leaks as it associates an ID to every memory allocation which is also printed out. This ID can be used to identify where the memory was allocated. All in all its a bit long winded i think ;), but it should work.

Share this post


Link to post
Share on other sites
Quote:
Original post by sphinx23
OT: okonomiyaki: Well, it didn't work in my case ;). The list was printed, an then the destructors were called, which i had verified by putting breakpoints in them. The CRT said there was memory leaked whereas in reality this memory was freed...


Strange, I remember it working for me, but I also now remember having problems with that. So you may be right, I might have gotten it working somehow by redesigning or something.

Quote:

But how does it work? What does it do? Does it generate a log file of some sort? And when it reports memory leaks, does it tell you where in the code those things were allocated?


Well, you are calling straight CRT functions, which is the object that handles your memory. It just keeps track of everything with memblocks and such. Look at the MSDN page if you want details. I'd highly recommend reading the _CrtDumpMemoryLeaks page also to be warned about what it does/does not do.

You can control where it outputs it. By default it's just VS.NET's output window. You can redirect it to print to the console, but I forgot how.

It doesn't really tell you where in code, just the locations of the memory leaks. It can be tricky to find them... Here's an example:

Detected memory leaks!
Dumping objects ->
{262} normal block at 0x00323430, 15 bytes long.
Data: <hello leak > 68 65 6C 6C 6F 20 6C 65 61 6B 00 CD CD CD CD
{261} normal block at 0x003233E0, 20 bytes long.
Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.


Share this post


Link to post
Share on other sites
The information is rather useless in that form...

But if you make the function run at the end of your program, is there any way to capture the output, and make something out of it?

Share this post


Link to post
Share on other sites
Quote:
Original post by Max_Payne
The information is rather useless in that form...

But if you make the function run at the end of your program, is there any way to capture the output, and make something out of it?


Not that I can really think of. You'd have to pipe stdout somehow and scan it. But even then, you'd be doing that before your program exits when all your global variables, and other things, are deallocated. So you'd get faulty memory leaks.

Personally, I've had no problem with this. It's quick and simple. You shouldn't ever have more than one or two sources of memory leaks. Code some, test it, ok you're fine, code some more, etc. I've never not been able to find a memory leak. You can also look at the length and type and say, "ah, look like an integer array of size 15. oh yeah, here's where I allocated it."

But yeah, you'll have to go back to overloaded new/delete if you really want full flexibility.

Share this post


Link to post
Share on other sites
Quote:
Original post by okonomiyaki
It doesn't really tell you where in code, just the locations of the memory leaks. It can be tricky to find them... Here's an example:
*** Source Snippet Removed ***


That's actually true, but there is a fix for that in like 5 rows (needs to be included in every file) that adds file and line to the checking (available in MFC, and available in normal C++ using this little fix)

Share this post


Link to post
Share on other sites
Are you truely overloading or are you just trying to redefine global operators new/delete? overloading != redefining.

Make sure you have you have your definitions with the correct signature aswell as for each of the default standard new/delete operators there are two versions one which has an exception specification which throws std::bad_alloc exception when new fails and the other which has a nothrow exception specification which returns 0 to inidicate failure.

Both version of all delete operators have a nothrow exception specifications where one takes a reference to std::nothrow_t as aswell as a pointer to void. The other just takes a pointer to void, so in your case you probably want to redefine the both versions:


#include <new>

void* operator new(std::size_t) throw(std::bad_alloc) { ... }
void* operator new(std::size_t, const std::nothrow_t&) throw() { ... }

void operator delete(void*, const std::nothrow_t&) throw() { ... }
void operator delete(void*) throw() {... }


Regarding the standard library containers, they are parameterized by allocator type. The allocator concept separates allocation/deallocation and construction/destruction for efficiency. The default allocator type used by the standard library containers is std::allocator, on GCC (and typically others) std::allocator uses global operators new/delete (for non multi-threaded apps) and uses it via explicit invocation to allocate/deallocate uninitialized memory, it uses placement new to do inplace construction of elements and explicitly invokes destructor for user-defined types.

Share this post


Link to post
Share on other sites
Quote:
Original post by okonomiyaki
Quote:
Original post by Max_Payne
The information is rather useless in that form...

But if you make the function run at the end of your program, is there any way to capture the output, and make something out of it?


Not that I can really think of. You'd have to pipe stdout somehow and scan it. But even then, you'd be doing that before your program exits when all your global variables, and other things, are deallocated. So you'd get faulty memory leaks.

Personally, I've had no problem with this. It's quick and simple. You shouldn't ever have more than one or two sources of memory leaks. Code some, test it, ok you're fine, code some more, etc. I've never not been able to find a memory leak. You can also look at the length and type and say, "ah, look like an integer array of size 15. oh yeah, here's where I allocated it."

But yeah, you'll have to go back to overloaded new/delete if you really want full flexibility.


I see that it says "long", in your output, but does it display custom type names (classes and structs)? If it does, then it could be a viable option. Especially if it actually displays the message after you program has run in the VS2003 window, because I actually do alot of testing when I program, and if it was displayed in an obvious manner, I would realistically notice the problem pretty quickly. I will look into it. This might be a simpler and more efficient solution.

Share this post


Link to post
Share on other sites

Doh, actually it doesn't specify types at all. It just says "20 bytes long," long just being the word long and not the type. You can tell by the contents of the block a lot of times though.

So there's a fix to make it display the function and/or line? Sweet...

I think this would be your best option, because there are a lot of flags you can set to change the way it displays it and where it displays it. You could probably get it to do something you want.

Share this post


Link to post
Share on other sites
Quote:
So there's a fix to make it display the function and/or line? Sweet...


Thats what I'm looking for. Still haven't found how. I also don't know for sure if it works with the Multi-Threaded Debug DLL runtime library (MSDN doesn't mention it, but the documentation might simplybe outdated).

Share this post


Link to post
Share on other sites
If you trust the implementation, you can overload new & delete in a base class and derive all your objects from that class. This can co-existing with other global memory tracking schemes that are present, for example, in MFC.

If you have an OO design, then all of your resources will be owned by some object - so you don't need to know that a vector leaked a bunch of vertices, you'll know that when you see the whole mesh object was leaked. If you keep a container of pointers to your objects, then you'll have to new them, and then they'll be tracked.

This is my memory tracker, it records the line, file, object name, & function name that allocated the object (it wouldn't be hard to add a counter to the overloaded new to keep track of that too):

#include <cstddef>

#ifndef NDEBUG
#include <typeinfo>
#endif

namespace mkh
{
namespace ood
{
struct Interface;

//Inheriting from this provides the requisite virtual dtor
// and consequentially enables RTTI for the derived object
struct Interface
{
//This is the only requirement for a polymorphic C++
// object (and it will be automatic in C++0x)
virtual ~Interface() {}

//new and delete overloads are provided to
// consolidate memory allocation. This prevents
// objects from being created with the context of one
// free-store, and destroyed in the context of another.
// This is particularily troublesome when using dynamic
// linking on Win32 (causes hard-crashes) - it is
// always a bug in your code to do this, you are
// corrupting the free-store. This fixes that problem
// because this new and delete overload will be invoked
// and the code in this library will create and destroy
// all ood objects.
// It also forces the allocation of objects to use the
// debug version for debug builds, allowing us to track
// memory usage and detect objects leaks.
// You should use mkh_new to 'new' objects that
// inherit from ood::Interface
#ifndef NDEBUG
//Use must use this debug version of operator new to create objects
#ifdef _MSC_VER
#define mkh_new(ObjectType) new(__LINE__,__FILE__, typeid(ObjectType), __FUNCTION__) ObjectType
#else
#define mkh_new(ObjectType) new(__LINE__,__FILE__, typeid(ObjectType)) ObjectType
#endif
static void* operator new
(size_t, int line, const char* file, const std::type_info&, const char* function =0);
static void* operator new[]
(size_t, int line, const char* file, const std::type_info&, const char* function =0);

//overloaded delete in case overloaded new throws
static void operator delete
(void*, int line, const char* file, const std::type_info&, const char* function);
static void operator delete[]
(void*, int line, const char* file, const std::type_info&, const char* function);
#else
#define mkh_new(ObjectType) new ObjectType
static void* operator new(size_t);
static void* operator new[](size_t);
#endif
//The delete's that typically are invoked
static void operator delete(void*);
static void operator delete[](void*);
};
}//ns ood
}//ns mkh




#include <map>
#include <stdexcept>
#include <sstream>
#include <cstdlib>
#include <fstream>
#include <typeinfo>
#include <algorithm>

#include "mkh/system/debug.hpp"
#include "mkh/system/exception.hpp"
#include "mkh/ood/interface.hpp"
#include "mkh/utility/stl_adaptors.hpp"

namespace mkh
{
namespace ood
{
#ifndef NDEBUG
typedef std::map<Interface*, mkh::debug::call_context> leak_map;
leak_map g_leaks;
leak_map g_array_leaks;

void* Interface::operator new(size_t size, int line, const char* file, const std::type_info& ti, const char* function)
{
void* space = ::malloc(size);
bool dup = !g_leaks.insert(std::make_pair(reinterpret_cast<Interface*>(space), debug::call_context(line, file, ti, function))).second;
return space;
}
void Interface::operator delete(void* space, int line, const char* file, const std::type_info&, const char* function)
{
size_t n = g_leaks.erase(reinterpret_cast<Interface*>(space));
MKH_ASSERT(n==1);
::free(space);
}
void* Interface::operator new[](size_t size, int line, const char* file, const std::type_info& ti, const char* function)
{
void* space = ::malloc(size);
bool dup = !g_array_leaks.insert(std::make_pair(reinterpret_cast<Interface*>(space), debug::call_context(line, file, ti, function))).second;
return space;
}

void Interface::operator delete[](void* space, int line, const char* file, const std::type_info& ti, const char* function)
{
size_t n = g_leaks.erase(reinterpret_cast<Interface*>(space));
MKH_ASSERT(n==1);
::free(space);
}
#else //NDEBUG
void* Interface::operator new(size_t size)
{
return ::malloc(size);
}

void* Interface::operator new[](size_t size)
{
return ::malloc(size);
}
#endif

void Interface::operator delete(void* space)
{
#ifndef NDEBUG
size_t n = g_leaks.erase(reinterpret_cast<Interface*>(space));
MKH_ASSERT(n=1);
#endif
::free(space);
}

void Interface::operator delete[](void* space)
{
#ifndef NDEBUG
size_t n = g_array_leaks.erase(reinterpret_cast<Interface*>(space));
MKH_ASSERT(n=1);
#endif
::free(space);
}

#ifndef NDEBUG
struct print_leak
{
print_leak(std::ostream& os) : os(os) {}
std::ostream& operator()(const leak_map::value_type& pair)
{
std::stringstream ss;
Interface* obj = pair.first;
const mkh::debug::call_context& cc = pair.second;
ss << cc.ti.name();
ss << " (0x" << obj << ") from " << cc.function;
ss << ": allocated on line " << cc.line;
ss << " in " << cc.file << "\n";

return os << ss.str();
}
std::ostream& os;
};

static const char dump_file[] = "interface_leaks.txt";
struct DumpInterfaceLeaks
{
DumpInterfaceLeaks()
{
std::fstream dump;
dump.open(dump_file, std::ios::out);
dump << "Running...\n";
}
~DumpInterfaceLeaks()
{
this->Dump();
}
void Dump()
{
std::fstream dump;
dump.open(dump_file, std::ios::out);

dump << "Leaked Objects: " << (int)g_leaks.size() << "\n";
mkh::utility::for_each(g_leaks, print_leak(dump));
dump.flush();

dump << "\nLeaked Object Arrays: " << (int)g_array_leaks.size() << "\n";
mkh::utility::for_each(g_array_leaks, print_leak(dump));
dump.flush();
}
} dump_interface_leaks;

#endif

}//ns ood
}//ns mkh



Share this post


Link to post
Share on other sites
Quote:
Original post by Shannon Barber
This is my memory tracker ...


Just want to point out a thew issues/tips with the code:


  • You probably want to have another version that does not have a virtual destructor but non virtual and defined as protected, the reason being if you not going to invoke delete on pointers to "Interface" and the type that is going to be tracked is a non polymorphic you end redundantly paying for it.

  • If your overloaded new operators does not throw exceptions then its signature should have nothrow exception specifications, operator delete must never throw exceptions thus should have a nothrow exception specification. By doing so not only is it semantically good it can be candidate for compiler optimizations believe it or not where as functions without any exception specifiction or ones with throwable types are not or less likely to have any kind of optimizations.

  • malloc/free is not the only way to deal with uninitialized memory (raw memory), if you explicitly invoke operator new (in this case the global ones not your overloaded ones) it will allocate uninitialized memory only. E.g.


    #include <new> // for placement new operator
    //...
    void* buf = ::operator new(sizeof(foo)); // allocates memory only
    ...
    foo* f = ::new(buf) foo(...); // constructs only, placement new
    ...
    f->~foo(); // destructs only, must be done for NON POD-types
    // with non trivial destructors
    //...
    ::operator delete(buf); // deallocates memory only


Share this post


Link to post
Share on other sites
In my case, anything that inherits from Interface will not only have virtual functions but will use virtual inheritence as well.

Is a no-throw specifier special? Because the run-time has to do work to verify that an exception thrown does not violate the exception specifier if there is one.
If C++ exceptions worked like Java exception I would expect performance benefits to a no-throw specifier. I think no-throw same-as no-specifier is best you can hope for in C++ though.

Thanks for the ::new(sizeof) tip though, I blissly ignored the fact that malloc can return zero.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
virtual destructors will be automatic in c++0x?

Share this post


Link to post
Share on other sites
Quote:
Original post by Shannon Barber
In my case, anything that inherits from Interface will not only have virtual functions but will use virtual inheritence as well.


Yeah then that is fine, i have a base that overloads the new/delete operators for class hierarchies to use a custom allocator i had cases where i wanted to use custom allocation for non polymorphic types as well as polymoprhic types.

This base is not intended to be used polymorphically (its sole purpose is for custom allocation) and i dont use pointers to this base, even if somebody else does i prevent delete from being called on them by making the destructor non virtual and protected. There is an article on gotw Virtuality has abit on the best time to make a base with destructor public virtual or non virtual and protected.

Quote:
Original post by Shannon Barber
Is a no-throw specifier special? Because the run-time has to do work to verify that an exception thrown does not violate the exception specifier if there is one.
If C++ exceptions worked like Java exception I would expect performance benefits to a no-throw specifier. I think no-throw same-as no-specifier is best you can hope for in C++ though.


When you have a nothrow exception spec you are informing the compiler that you guarantee that the function doesn't therefor the compiler can make optimizations if it wishes to do so thats not to say everyone should go rush off do it every where i think its just good practice to follow how the standard does with its default global new/delete operators where for each of pair of new/delete there is one which throws exceptions another which returns 0 to indiciate failure. If a function signature does not have any exception spec it is not the same as no throw exception spec.

Quote:
Original post by Shannon Barber
I blissly ignored the fact that malloc can return zero.


Explicitly invoking the plain global operator new it will throw std::bad_alloc, if you want the nothrow (which returns 0 to indiciate failure) then it would be:


#include <new> // std::nothrow
void* buf = ::operator new(sizeof(foo), std:nothrow);
//....
::operator delete(buf, std::nothrow);

Share this post


Link to post
Share on other sites
Quote:
Original post by snk_kid
<snip>
When you have a nothrow exception spec you are informing the compiler that you guarantee that the function doesn't... <snip>


That's not standard, are you talking about MSVC behavior?

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this