Overriding C++ new and delete operators to make garbage collector

Started by
21 comments, last by GameDev.net 19 years, 8 months ago
OK, i had an idea for a program I'm current having memory problems with (and i was being extremely careful with memory too!): Is there any way to override the new operator so that it allocates memory for the object, and then adds that object to a vector? Also, for the delete operator, can I override it to remove that object from the vector and then free the memory? The plan is, at the end of the program I loop through the vector to see what objects are still there and thereby find my memory leaks. I already know that you can override new and delete but am a bit iffy with the details. Any ideas on how to go about this? or is it a bad idea in general?
-----------------------------------------------------What do you call people who are afraid of Santa Claus? Claustrophobic!
Advertisement
I've recently been working on a memory manager just like you.

First of all i created a simple class to store the information of a memory block, including some details for logging purposes.

I added the following information:
- Filename of where the allocation was made
- Function name
- Line number
- The size of the memory block in bytes
- The address of the memory block

The previous 2 items can be easily obtained from the new operator


I then made a memory manager class, which i implemented as a singleton.
That class contained a std::map<void*, CMemoryBlock> list. I did this, so i could access the memory block data by the address which is accessible by the new and delete operators.

Lastly i overloaded the new operator and made sure the implementation follows the C++ standard.

It basicly worked like this:
void *operator new(size_t Size, std::string File, std::string Function, long Line){    // Allocate the block of memory     void* Addr = malloc(Size);    ...    // Register the memory block    CMemoryManager::GetInstance()->RegisterMemBlock(File, Func, Line, Addr, Size);    return Addr;}



Unfortunately the delete operator can't be overloaded, so i implemented delete as follows:

void operator delete(void* Addr){    // Release the memory    free(Addr);    // Unregister the memory block    CMemoryManager::GetInstance()->UnregisterMemBlock(Addr);}



This is all working nicely... the last thing to do, was creating a macro for the new operator, so the file, function and line parameters got filled in automaticly.

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


The last thing you need to do is create a function which prints out the registered memory blocks when the program ends. I did this in the destructor of the memory manager class.

And there you have it, your own memory manager.
Hope this helps
wow!!!

thats almost exactly what im looking for!

just a quickie: the ... in your operator* new method, what goes there?

thanks a LOT for the reply... this will help tremendously!!!!!
-----------------------------------------------------What do you call people who are afraid of Santa Claus? Claustrophobic!
Quote:Original post by groveler
wow!!!

thats almost exactly what im looking for!

just a quickie: the ... in your operator* new method, what goes there?

thanks a LOT for the reply... this will help tremendously!!!!!


You wanna look into overloading placement new operator there is some faqs about it here. There are a couple of things you need to think about, do you wont to overload for a particular user-defined type or for all types. If a particular user-defined type then you can overloaded it within the class declaration, if it's for all then overload placement new operator declared in header new.
There's a robust Memory Manager over on Flipcode:Ask Midnight. It performs stress testing on your app too. It's hardcore stuff ;)
I'd just like to point out that while Garbage Collection is a subcategory of Memory Management, it does not seem to be the kind of Memory Management you want to do. Well, I guess you could call these Garbage Collectors, but 'real' Garbage Collectors free memory in 'realtime' as you no longer need it, while these things being discussed (from what I can tell) only free unfreed memory when the program exits. This is good for debugging and making sure there are no leaks, but it doesn't provide quite the same benefits as 'real' Garbage Collection.

-Extrarius
I guess i can just post my code while i'm at it...

Maybe i can even get some feedback from the pro's :-)

Code for: Memory.h
// Inclusion Guard#ifndef MEMORY_H_INCLUDED#define MEMORY_H_INCLUDED	// System Includes	#include <windows.h>	#include <string>	#include <map>	#include <new>	// Macros	#ifdef _DEBUG		// Overloaded new and delete operators		#define DEBUG_NEW new(__FILE__, __FUNCTION__, __LINE__)	#else		// Default new and delete operators		#define DEBUG_NEW new	#endif	// Undefine the new and delete operators	#undef new	#undef delete	class CMemoryBlock	{	protected:		// Protected Functions		// Constructor		CMemoryBlock(std::string File, std::string	Func, long Line, void* Addr, size_t Size);		// Output operator, used when writing the memory block to a file		friend std::ostream &operator<<(std::ostream &Out, CMemoryBlock &Rhs);		// File information		std::string		m_File;		std::string		m_Func;		long			m_Line;		// Memory information		void*			m_Addr;		size_t			m_Size;		SYSTEMTIME		m_Time;	private:		// Only memory manager can create instances of a memory block		friend class CMemoryManager;	};	template<class T>	class AutoPtr;	class CMemoryManager	{	public:		// Public Functions		// Returns a reference to an instance of the memory manager		static CMemoryManager &GetInstance();		// Writes all memory leaks to a file		void LogMemoryLeaks();	protected:		// Protected Functions		// Destructor		~CMemoryManager();		// Registers a memory block		void RegisterMemoryBlock(std::string File, std::string Func, long Line, void* Addr, size_t Size);		// Unregisters a memory block		void UnregisterMemoryBlock(void *Addr);	private:		// Private Variables		// List of registered memory block		std::map<void*, CMemoryBlock>	m_MemoryList;		// Instance of the memory manager		static CMemoryManager			m_Instance;		// Only the new and delete operators have access to the protected members		friend void *operator new(size_t Size, std::string File, std::string Func, long Line);		friend void *operator new[](size_t Size, std::string File, std::string Func, long Line);		friend void operator delete(void *Ptr);		friend void operator delete[](void *Ptr);	};	// Overloaded new operators	void *operator new(size_t Size, std::string File, std::string Func, long Line);	void *operator new[](size_t Size, std::string File, std::string Func, long Line);	// Overridden delete operators	void operator delete(void *Ptr);	void operator delete[](void *Ptr);	// Set the new operator	#define new	DEBUG_NEW#endif	// MEMORY_H_INCLUDED


Code for Memory.cpp

// System Includes
#include <direct.h>
#include <fstream>

// Project Includes
#include "Memory.h"


// Macros
#ifdef _DEBUG
// Assert which inserts a break point
#define Assert(x) if ( (x) == false ) __asm { int 3; }
#else
// Empty assert
#define Assert(x)
#endif


// Undefine the new and delete operators
#undef new
#undef delete



// Constructor
CMemoryBlock::CMemoryBlock(std::string File, std::string Func, long Line, void* Addr, size_t Size):
m_Func(Func),
m_Line(Line),
m_Addr(Addr),
m_Size(Size)
{
// Get the file name
char WDir[260];
getcwd(WDir, 260);
m_File = File.substr(strlen(WDir) + 1);

// Format the function name
m_Func += "()";

// Get the current time of when the allocation was made
GetLocalTime(&m_Time);
}


// Output operator, used when writing the memory block to a file
std::ostream &operator<<(std::ostream &Out, CMemoryBlock &Rhs)
{
Out << "--------------------------------------------------\n";
Out << "File:\t" << Rhs.m_File << "\n";
Out << "Func:\t" << Rhs.m_Func << "\n";
Out << "Line:\t" << Rhs.m_Line << "\n\n";
Out << "Date:\t" << Rhs.m_Time.wDay << "-" << Rhs.m_Time.wMonth << "-" << Rhs.m_Time.wYear << "\n";
Out << "Time:\t" << Rhs.m_Time.wHour << ":" << Rhs.m_Time.wMinute << ":" << Rhs.m_Time.wSecond << " - " << Rhs.m_Time.wMilliseconds << "\n\n";
Out << "A memory block of " << (int)Rhs.m_Size << " bytes has not been properly deleted.\nThe memory still exists on memory address " << Rhs.m_Addr << "\n";
Out << "--------------------------------------------------\n\n\n";
return Out;
}


// Public Functions

// Returns a reference to an instance of the memory manager
CMemoryManager &CMemoryManager::GetInstance()
{
return m_Instance;
}


// Writes all memory leaks to a file
void CMemoryManager::LogMemoryLeaks()
{
// Iterator
std::map<void*, CMemoryBlock>::iterator i;

// Open the file for reading
std::ofstream fout("Memory.log");
if ( fout.is_open() )
{
// Check if any memory leaks have been found
if ( m_MemoryList.size() == 0 )
fout << "No memory leaks have been detected.";
else
// Write all memory blocks to the log
for ( i = m_MemoryList.begin(); i != m_MemoryList.end(); ++i )

// Write the memory block to the file
fout << i->second;

// Close the file for writing
fout.close();
}
}


// Protected Functions

// Destructor
CMemoryManager::~CMemoryManager()
{
// Write the memory leaks to a file
LogMemoryLeaks();

// Open the log file
ShellExecute(NULL, "open", "Memory.log", NULL, NULL, SW_SHOWDEFAULT);
}


// Registers a memory block
void CMemoryManager::RegisterMemoryBlock(std::string File, std::string Func, long Line, void* Addr, size_t Size)
{
// Add the block of memory to the list
m_MemoryList.insert(std::make_pair(Addr, CMemoryBlock(File, Func, Line, Addr, Size)));
}


// Unregisters a memory block
void CMemoryManager::UnregisterMemoryBlock(void *Addr)
{
m_MemoryList.erase(Addr);
}


// Instance of the memory manager
CMemoryManager CMemoryManager::m_Instance;



// Overloaded new operators
void *operator new(size_t Size, std::string File, std::string Func, long Line)
{
// Make sure a valid size is being allocated
if ( Size == 0 ) Size = 1;

// Allocate a piece of memory
void *Address = NULL;
while ( !(Address = malloc(Size)) )
{
new_handler Handler = std::set_new_handler(0);
std::set_new_handler(Handler);

if ( Handler)
Handler();
else
throw std::bad_alloc();
}

// Register the newly created memory block
CMemoryManager::GetInstance().RegisterMemoryBlock(File, Func, Line, Address, Size);

// Return the memory address
return Address;
}


void *operator new[](size_t Size, std::string File, std::string Func, long Line)
{
// Make sure a valid size is being allocated
if ( Size == 0 ) Size = 1;

// Allocate a piece of memory
void *Address = NULL;
while ( !(Address = malloc(Size)) )
{
new_handler Handler = std::set_new_handler(0);
std::set_new_handler(Handler);

if ( Handler)
Handler();
else
throw std::bad_alloc();
}

// Register the newly created memory block
CMemoryManager::GetInstance().RegisterMemoryBlock(File, Func, Line, Address, Size);

// Return the memory address
return Address;
}


// Overridden delete operator
void operator delete(void *Ptr)
{
if ( Ptr )
{
// Unregister the memory block, because it has been properly deleted
CMemoryManager::GetInstance().UnregisterMemoryBlock(Ptr);

// Release the memory
free(Ptr);
}
}


// Overridden delete operator
void operator delete[](void *Ptr)
{
if ( Ptr )
{
// Unregister the memory block, because it has been properly deleted
CMemoryManager::GetInstance().UnregisterMemoryBlock(Ptr);

// Release the memory
free(Ptr);
}
}



I suggest you study this piece of code and try to implement your own version and don't just copy and paste.
Only refer to my code when you're stuck, this way you will learn the most.

Please let me know if you have any questions. I'll be glad to help.

Joost.


Washu: Changed code tags to source tags.


[Edited by - Washu on August 4, 2004 7:33:07 PM]
Nixsos: You really should use the [source] and [/source] tags to reduce post length
"Walk not the trodden path, for it has borne it's burden." -John, Flying Monk
Stupid internall 500 errors.. for the 2nd time... lets try this again...



Included are my logging and memory tracking files. Simply Include "MyMem.h" and it will write out the allocations/deallocations to a log file specified in the Logger.Init call. It tracks the address and size of each allocation, reports at the end of the program if there is any memory not deleted, automatically deletes it for you so you don't have any leaks (but you don't want to rely on it!), and also tells you the maximum amount of memory you're program allocated at one time :). It does NOT count it's own memory however, so it will be an accurate value whether you're using it or not (well, accurate as in telling you how much you're program is allocating).

/* MyMem.h */#ifndef MyMem_H#define MyMem_H//Set our memory to debuf if we're in debug mode build!//Uncomment to let it debug memory only in debug builds (in msvc anyways)//#ifdef _DEBUG#define Mem_Debug//#endif#ifdef Mem_Debug#include "Logger.h"struct MemList_S{	void *Location;	unsigned long Size;	MemList_S *prv, *nxt;};class MemTracker_C{public:	MemList_S *MemList_Head;	unsigned long MemCounter;	unsigned long MemAllocated;	unsigned long MaxAllocated;	Logger_C Logger;public:	MemTracker_C(void);	void Add(void *ptr, unsigned long Size);	void Remove(void *ptr);	~MemTracker_C(void);};void *operator new(size_t size);void operator delete(void *ptr);void *operator new[](size_t size);void operator delete[](void *ptr);extern MemTracker_C MemTracker;#endif#endif


/* MyMem.cpp */#include <stdio.h>#include <stdarg.h>#include <stdlib.h>#include "MyMem.h"#ifdef Mem_DebugMemTracker_C MemTracker;MemTracker_C::MemTracker_C(void){	MemCounter=0;	MemAllocated=0;	MaxAllocated=0;	MemList_Head = 0; //Set us to NULL//Clear our file out	Logger.Init("Memory.log");	Logger.Write("Memory Tracker Started...");}void MemTracker_C::Add(void *ptr, unsigned long size){	MemList_S *ptrMemObject;	ptrMemObject = (MemList_S*)malloc(sizeof(MemList_S)); //Allocate an object!	ptrMemObject->Location = ptr;	ptrMemObject->Size = size;	MemAllocated += size;	if (MemAllocated > MaxAllocated)		MaxAllocated=MemAllocated;	if (!MemList_Head) //Nothing yet!	{		ptrMemObject->nxt = ptrMemObject;		ptrMemObject->prv = ptrMemObject;		MemList_Head = ptrMemObject;	}	else //Otherwise, lets add it to the end!	{		ptrMemObject->nxt = MemList_Head;		ptrMemObject->prv = MemList_Head->prv;		MemList_Head->prv->nxt = ptrMemObject;		MemList_Head->prv = ptrMemObject;	}	Logger.Write("Added - %x %u",(unsigned long)ptr,size);}void MemTracker_C::Remove(void *ptr){	if (MemList_Head) //We have a list?	{		if (MemList_Head->Location == ptr) //first one?		{			if (MemList_Head->nxt == MemList_Head) //Last one in list?			{				MemAllocated-= MemList_Head->Size;				Logger.Write("Removed - %x %u",MemList_Head->Location,MemList_Head->Size);				free(MemList_Head);				MemList_Head = 0;			}			else //We have others in our list			{				MemAllocated-= MemList_Head->Size;				Logger.Write("Removed - %x %u",MemList_Head->Location,MemList_Head->Size);				MemList_S *ptrMemList = MemList_Head->nxt;				MemList_Head->nxt->prv = MemList_Head->prv;				MemList_Head->prv->nxt = MemList_Head->nxt;				free(MemList_Head);				MemList_Head = ptrMemList;			}		}		else //Ok, this isn't our first one!		{			MemList_S *ptrMemList = MemList_Head->nxt;			while (ptrMemList!=MemList_Head)			{				if (ptrMemList->Location == ptr)				{					MemAllocated-= ptrMemList->Size;					Logger.Write("Removed - %x %u",ptrMemList->Location,ptrMemList->Size);					ptrMemList->nxt->prv = ptrMemList->prv;					ptrMemList->prv->nxt = ptrMemList->nxt;					free(ptrMemList);					break;				}				ptrMemList = ptrMemList->nxt;			}		}	}}MemTracker_C::~MemTracker_C(void){	if (MemCounter!=0)	{		Logger.Write("\n*** Summary ***");		Logger.Write("Final memory allocated %d - %d bytes",MemCounter,MemAllocated);		while (MemList_Head) //While we have memory allocated!		{			Remove(MemList_Head->Location); //Lets remove it from our list!		}	}	else		Logger.Write("\n*** Summary *** - All memory accounted for!");		Logger.Write("Maximum memory allocated %d bytes",MaxAllocated);	MemCounter = 0;}void *operator new(size_t size){	void *ptr = malloc(size);	++MemTracker.MemCounter;	MemTracker.Add(ptr,size);	return ptr;}void operator delete(void *ptr){	if (MemTracker.MemCounter)	{		--MemTracker.MemCounter;		MemTracker.Remove(ptr);	}	free(ptr);}void *operator new[](size_t size){	void *ptr = malloc(size);	++MemTracker.MemCounter;	MemTracker.Add(ptr,size);	return ptr;}void operator delete[](void *ptr){	if (MemTracker.MemCounter)	{		--MemTracker.MemCounter;		MemTracker.Remove(ptr);	}	free(ptr);}#endif


/* Logger.h"#ifndef Logger_H#define Logger_H#include <stdio.h>class Logger_C{public:  FILE *LogFile;  char Enabled;  char *LogFileName;public:  Logger_C(void);  void Init(char *fName);  void Write(const char *str, ...);  ~Logger_C(void);};#endif


/* Logger.cpp */#include "Logger.h"#include <stdarg.h>Logger_C::Logger_C(void){	Enabled = 0;}void Logger_C::Init(char *fName){	Enabled = 1;	LogFileName = fName;	LogFile = fopen(LogFileName,"wb");	//Clear file	fclose(LogFile);	//Close our file	Write("Logging system started");}void Logger_C::Write(const char *str, ...){	va_list args;	if (Enabled && LogFileName)	{		va_start(args,str);		LogFile = fopen(LogFileName,"awb"); //append write binary		vfprintf(LogFile,str,args);		fprintf(LogFile,"\n");		fclose(LogFile);		va_end(args);	}}Logger_C::~Logger_C(void){}


[Edited by - Ready4Dis on August 4, 2004 8:19:03 PM]
Stop reinventing the wheel guys. If a few people have thought of the same thing the chances are it's been researched to death already. This is the first link on google for "c++ garbage collection":
http://www.hpl.hp.com/personal/Hans_Boehm/gc/

This topic is closed to new replies.

Advertisement