My goal here is to write a small class to keep track of memory I allocate/deallocate so that I can more easily find memory leaks. To do this I overloaded the operators new, new[], delete, and delete[]. In those methods, I allocate/free memory as usual, but also make a call to my MemoryTracker object which stores this information. The MemoryTracker is a singleton.
My problem is that whenever I call new, the Allocate() method in my memory tracker inserts into a map which ends up calling new, thus causing infinite recursion. What I am looking for is an elegant solution around this that will work in linux/windows and in both single-threaded and multi-threaded environments.
So far I've had a few ideas:
1. Right before inserting into the map, set a flag in the memory tracker to ignore tracking the next allocation. Unfortunately this won't work in a multi-threaded environment.
2. Somehow grab the pointer of the pair object as it's being created and tell the memory tracker to ignore it. I don't think this is possible, since we don't have the pointer to the object until we are inside operator new.
3. Inside operator new, determine what kind of pointer is being created. If it is of type pair< void*, size_t >, then ignore it. The problem here is that I can never use pair< void*, size_t > ever again anywhere in my projects.
Here is my main:
#include <cstdlib>
#include <iostream>
#include "MemoryTracker.h"
#include "overloaded_new_delete.h"
using namespace std;
MemoryTracker* memory_tracker = 0;
int main(int argc, char *argv[]) {
// Create the memory tracker
memory_tracker = MemoryTracker::Instance();
char* test0 = new char( 'a' );
memory_tracker->PrintStatus();
memory_tracker->Destroy();
system("PAUSE");
return EXIT_SUCCESS;
}
Here are my overloaded operators (header file):
#ifndef __OVERLOAD_NEW_DELETE_H__
#define __OVERLOAD_NEW_DELETE_H__
#include <iostream>
#include <cstdlib>
#include "MemoryTracker.h"
using namespace std;
extern MemoryTracker* memory_tracker;
void* operator new( size_t size );
void* operator new[]( size_t size );
void operator delete( void *p );
void operator delete[]( void *p );
#endif
Here are my overloaded operators (implementation):
#include "overloaded_new_delete.h"
void* operator new( size_t size ) {
void *p = malloc( size );
if (!p)
throw "operator new() error";
cout << p << endl;
cout << size << endl;
system("pause");
if( memory_tracker != 0 ) {
memory_tracker->Allocate( p, size );
}
return p;
}
void* operator new[]( size_t size ) {
void *p = malloc(size);
if (!p)
throw "operator new() error";
if( memory_tracker != 0 ) {
memory_tracker->Allocate( p, size );
}
return p;
}
void operator delete( void *p ) {
free(p);
if( memory_tracker != 0 ) {
memory_tracker->Free( p );
}
}
void operator delete[]( void *p ) {
free(p);
if( memory_tracker != 0 ) {
memory_tracker->Free( p );
}
}
Here is my MemoryTracker class header file:
#ifndef __MEMORY_TRACKER_H__
#define __MEMORY_TRACKER_H__
#include <iostream>
#include <map>
#include <fstream>
using namespace std;
class MemoryTracker {
public:
static MemoryTracker* Instance();
static MemoryTracker* Instance( const string& filename );
void Destroy();
void Allocate( const void* ptr, const size_t size );
void Free( const void* ptr );
void PrintStatus();
protected:
MemoryTracker();
~MemoryTracker();
private:
static MemoryTracker* _instance;
map<const void *, const size_t> _memory_map;
static string _OUTPUT_FILENAME;
size_t _total_size;
int _num_pointers;
};
#endif
Here is my MemoryTracker class implementation:
#include "MemoryTracker.h"
// Initialize instance pointer
MemoryTracker* MemoryTracker::_instance = 0;
string MemoryTracker::_OUTPUT_FILENAME = "memorytracker.log";
/**
* CONSTRUCTOR
**/
MemoryTracker::MemoryTracker() {
_memory_map.clear();
_num_pointers = 0;
_total_size = 0;
}
/**
* DESTRUCTOR
**/
MemoryTracker::~MemoryTracker() {
_memory_map.clear();
_num_pointers = 0;
_total_size = 0;
}
/**
* Destroys the instance.
**/
void MemoryTracker::Destroy() {
delete _instance;
_instance = 0;
}
/**
* Returns the instance of this singleton.
**/
MemoryTracker* MemoryTracker::Instance() {
if( _instance == 0 ) {
_instance = new MemoryTracker;
}
return _instance;
}
/**
* Call this when allocating memory with overloaded new or new[].
**/
void MemoryTracker::Allocate( const void* ptr, const size_t size ) {
_memory_map.insert( pair<const void*, const size_t>( ptr, size ) );
_total_size += size;
_num_pointers++;
}
/**
* Call this when freeing memory with delete or delete[].
**/
void MemoryTracker::Free( const void* ptr ) {
size_t size = _memory_map[ ptr ];
_memory_map.erase( ptr );
_total_size -= size;
_num_pointers--;
}
/**
* Prints the status of all the allocated memory being tracked to file.
**/
void MemoryTracker::PrintStatus() {
// Open the log file for output
ofstream out( _OUTPUT_FILENAME.c_str() );
// Print Totals
out << "Total Number of Pointers: " << _num_pointers << endl;
out << "Total Size Allocated: " << _total_size << endl;
out << endl << endl;
// Print specific info about each pointer
out << "Pointer Information:" << endl;
map< const void*, const size_t >::iterator it;
for( it = _memory_map.begin(); it != _memory_map.end(); it++ ) {
out << "Address: " << it->first << " Size: " << it->second << endl;
}
// Close log file
out.close();
}