Skip to content

Instantly share code, notes, and snippets.

@nicholaschiasson
Last active July 9, 2019 09:36
Show Gist options
  • Save nicholaschiasson/85cd296f34c5a2728c5a61fb426242f8 to your computer and use it in GitHub Desktop.
Save nicholaschiasson/85cd296f34c5a2728c5a61fb426242f8 to your computer and use it in GitHub Desktop.
Memory Monitors for C++

MemMon Memory Monitors

NOT A GARBAGE COLLECTOR

I decided to try my hand at making my code a bit safer as far as managing weak references goes, so I tried to make something to track references to memory locations. I soon thought that it was a stupid idea and simplified it so that it was simply the memory locations' validity which were being monitored. That way, all I needed to do was essentially a custom null check to see if my weak references were still valid. I tried to do this in the simplest and most aesthetically pleasing way as possible. Naturally, that means (to me) attempting to overload the new and delete keywords for every possible type. Well, since you can't do that, I decided to use macros :)

I also came up with two seperate static classes which are only really different in that they use two different data structures.

Usage

Setup

  • Import the .cpp files into your project source and the .h files into your project headers.
  • Include only the MemMon.h file wherever you plan on using it.
  • Inside of MemMon.h is where you set your desired implementation (map, set, or none).
    • Find the macro definition for MEMMON_TYPE and set it to one of MAPMEMMON_TYPE, SETMEMMON_TYPE, or NULL_MEMMON_TYPE.
  • From now on, instead of new, delete, new[], and delete[] keywords, use mm_new, mm_delete, mm_new_a, and mm_delete_a, respectively.

Keyword Macros

void * mm_new(constructor)

MemMon new expects to be passed a constructor of the type to allocate.

Example usage: Foo *foo = mm_new(Foo(args));

void * mm_new_a(type, count)

MemMon new array expects to be passed a type and the number of elements of that type to allocate.

Example usage: Foo *foo_arr = mm_new_a(Foo, 25);

void mm_delete(data_ptr)

MemMon delete expects to be passed a pointer to the data to delete.

Example usage: mm_delete(foo);

void mm_delete_a(array_data_ptr)

MemMon delete array expects to be passed a pointer to the data to delete where the data was allocated as an array.

Example usage: mm_delete_a(foo_arr);

bool mm_null(data)

MemMon null check expects to be passed a pointer to the data to check if that data is valid or null.

Example usage: bool dataIsNull = mm_null(foo);

size_t mm_sizeof(data)

MemMon size expects to be passed a pointer to some data and will check how many bytes were allocated to that address. This works effectively on arrays allocated with mm_new_a just as well as single chunks of data allocated with mm_new. This macro is only available with MEMMON_TYPE set to MAPMEMMON_TYPE.

Example usage: size_t foo_size = mm_sizeof(foo);

size_t mm_count(data)

MemMon element count expects to be passed a pointer to some data and will treat that data as an array, checking how many elements were allocated to the array. Note, this macro is really for arrays, but can be used with regular pointers allocated with mm_new - the output in that case will always be 1. This macro is only available with MEMMON_TYPE set to MAPMEMMON_TYPE.

Example usage: size_t foo_arr_count = mm_count(foo_arr);

SetMemMon

This one represents my first iteration on this idea. Using a set, it simply keeps track of which memory locations are still not freed. Although the next iteration is more useful, I kept this one around just because of the slight speed compromise.

MapMemMon

Using a map, I stored a pointer to the address as the key and the number of bytes to be stored as the value. This way, I could ALSO perform a sizeof on any dynamically allocated arrays, and expanding on that, an element count as well! I am happy with the results and think that I will likely be using MapMemMon all the time in the future.

Testing Results

So for performance testing, among other stress tests, I basically just did a bunch of new array and delete array calls in a loop and compared the clock times with regular new[] and delete[] calls. I would say I am happy enough with the results, given the overhead. Should there be any curiosity, the following is the average of my results for a run of speedtest.cpp:

  • Regular new[] and delete[]: 160 clicks for 1000 allocated and deleted Foo arrays of size 1000
  • SetMemMon new[] and delete[]: 370 clicks for 1000 allocated and deleted Foo arrays of size 1000
  • MapMemMon new[] and delete[]: 580 clicks for 1000 allocated and deleted Foo arrays of size 1000
MIT License
Copyright (c) 2017 Nicholas Omer Chiasson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
OS := $(shell uname)
ifneq "$(OS)" "Darwin"
STATIC_FLAGS += -static
endif
speedtest_static: libmemmon.a
g++ -std=c++11 $(STATIC_FLAGS) speedtest.cpp -L${PWD} -lmemmon -o speedtest
speedtest_dynamic: libmemmon.so
g++ -std=c++11 speedtest.cpp -L${PWD} -lmemmon -o speedtest
libmemmon.a:
g++ -c -std=c++11 SetMemMon.cpp MapMemMon.cpp MemMon.cpp
ar rcs libmemmon.a SetMemMon.o MapMemMon.o MemMon.o
rm *.o
libmemmon.so:
g++ -fPIC -shared -std=c++11 SetMemMon.cpp MapMemMon.cpp MemMon.cpp -o libmemmon.so
clean:
rm -f speedtest *.so *.o *.a
#include "MapMemMon.h"
using namespace std;
std::map<void *, size_t> * MapMemMon::allocatedMemory = 0;
void MapMemMon::Initialize()
{
if (allocatedMemory == 0)
{
allocatedMemory = new std::map<void *, size_t>();
}
}
void MapMemMon::Cleanup()
{
if (allocatedMemory != 0)
{
delete allocatedMemory;
allocatedMemory = 0;
}
}
bool MapMemMon::Null(void * address)
{
bool rc = (allocatedMemory == 0);
rc = (!rc ? allocatedMemory->find(address) == allocatedMemory->end() : rc);
return rc;
}
size_t MapMemMon::SizeOf(void * data)
{
size_t s = 0;
if (!Null(data))
{
s = (*allocatedMemory)[data];
}
return s;
}
#ifndef MAPMEMMON_H
#define MAPMEMMON_H
#include <cstdlib>
#include <type_traits>
#include <map>
/**
* SetMemMon - Memory monitor using a stl map
*
* It is not recommended to use the macros and functions in this file directly.
* Instead, use MemMon.h and set the MEMMON_TYPE preprocessor definition to
* MAPMEMMON_TYPE.
*/
/**
* MapMemMon new
*/
#ifndef _mmm_new
#define _mmm_new(constructor) MapMemMon::New<decltype(constructor)>(new constructor, 1)
#endif // _mmm_new
/**
* MapMemMon new array
*/
#ifndef _mmm_new_a
#define _mmm_new_a(type, n) MapMemMon::New<type>(new type[n](), n)
#endif // _mmm_new_a
/**
* MapMemMon delete
*/
#ifndef _mmm_delete
#define _mmm_delete(data) MapMemMon::Delete<std::remove_reference<decltype(*data)>::type>(data)
#endif // _mmm_delete
/**
* MapMemMon null check
*/
#ifndef _mmm_null
#define _mmm_null MapMemMon::Null
#endif // _mmm_null
/**
* MapMemMon memory size
*/
#ifndef _mmm_sizeof
#define _mmm_sizeof(data) MapMemMon::SizeOf((void *)data)
#endif // _mmm_sizeof
/**
* MapMemMon element count
*/
#ifndef _mmm_count
#define _mmm_count(data) mm_sizeof((void *)data)/sizeof(decltype(*data))
#endif // _mmm_count
class MapMemMon
{
public:
virtual ~MapMemMon() = 0;
static void Initialize();
static void Cleanup();
template<typename T>
static T * New(T * data, size_t count);
template<typename T>
static size_t Delete(T * data);
static bool Null(void * address);
static size_t SizeOf(void * data);
private:
static std::map<void *, size_t> * allocatedMemory;
};
template<typename T>
inline T * MapMemMon::New(T * data, size_t count)
{
if (allocatedMemory == 0)
{
Initialize();
}
if (allocatedMemory != 0 && data != 0 && count > 0)
{
(*allocatedMemory)[(void *)data] = sizeof(T) * count;
}
else
{
if (data != 0)
{
if (count > 1)
{
delete[] data;
}
else
{
delete data;
}
data = 0;
}
}
return data;
}
template<typename T>
inline size_t MapMemMon::Delete(T * data)
{
size_t bytesDeleted = 0;
size_t dataSize = SizeOf(data);
size_t dataCount = dataSize / sizeof(T);
if (data != 0)
{
if (allocatedMemory != 0)
{
allocatedMemory->erase((void *)data);
bytesDeleted = dataSize;
if (allocatedMemory->empty())
{
Cleanup();
}
}
if (dataCount > 1)
{
delete[] data;
}
else
{
delete data;
}
}
return bytesDeleted;
}
#endif // MAPMEMMON_H
#include "MemMon.h"
void MemMon::Cleanup()
{
SetMemMon::Cleanup();
MapMemMon::Cleanup();
}
#ifndef MEMMON_H
#define MEMMON_H
#include "SetMemMon.h"
#include "MapMemMon.h"
/**
* MemMon - Memory monitor
*
* It is recommended to use the macros in this file rather than those in the
* included files directly.
*/
#ifndef NULL_MEMMON_TYPE
#define NULL_MEMMON_TYPE 0
#endif // NULL_MEMMON_TYPE
#ifndef SETMEMMON_TYPE
#define SETMEMMON_TYPE 1
#endif // SETMEMMON_TYPE
#ifndef MAPMEMMON_TYPE
#define MAPMEMMON_TYPE 2
#endif // MAPMEMMON_TYPE
/**
* Use this to set the type of memory monitoring to use.
*/
#ifndef MEMMON_TYPE
#define MEMMON_TYPE MAPMEMMON_TYPE
#endif // MEMMON_TYPE
/**
* MemMon new
*/
#if MEMMON_TYPE==SETMEMMON_TYPE
#ifndef mm_new
#define mm_new _smm_new
#endif
#elif MEMMON_TYPE==MAPMEMMON_TYPE
#ifndef mm_new
#define mm_new _mmm_new
#endif
#else
#ifndef mm_new
#define mm_new(constructor) new constructor
#endif
#endif // mm_new
/**
* MemMon new array
*/
#if MEMMON_TYPE==SETMEMMON_TYPE
#ifndef mm_new_a
#define mm_new_a _smm_new_a
#endif
#elif MEMMON_TYPE==MAPMEMMON_TYPE
#ifndef mm_new_a
#define mm_new_a _mmm_new_a
#endif
#else
#ifndef mm_new_a
#define mm_new_a(type, n) new type[n]()
#endif
#endif // mm_new_a
/**
* MemMon delete
*/
#if MEMMON_TYPE==SETMEMMON_TYPE
#ifndef mm_delete
#define mm_delete _smm_delete
#endif
#elif MEMMON_TYPE==MAPMEMMON_TYPE
#ifndef mm_delete
#define mm_delete _mmm_delete
#endif
#else
#ifndef mm_delete
#define mm_delete(data) if(data!=0){delete data;data=0;}
#endif
#endif // mm_delete
/**
* MemMon delete array
*/
#if MEMMON_TYPE==SETMEMMON_TYPE
#ifndef mm_delete_a
#define mm_delete_a _smm_delete_a
#endif
#elif MEMMON_TYPE==MAPMEMMON_TYPE
#ifndef mm_delete_a
#define mm_delete_a _mmm_delete
#endif
#else
#ifndef mm_delete_a
#define mm_delete_a(data) if(data!=0){delete[] data;data=0;}
#endif
#endif // mm_delete_a
/**
* MemMon null check
*/
#if MEMMON_TYPE==SETMEMMON_TYPE
#ifndef mm_null
#define mm_null _smm_null
#endif
#elif MEMMON_TYPE==MAPMEMMON_TYPE
#ifndef mm_null
#define mm_null _mmm_null
#endif
#else
#ifndef mm_null
#define mm_null(data) (data == 0)
#endif
#endif // mm_null
/**
* MemMon memory size
*/
#if MEMMON_TYPE==MAPMEMMON_TYPE
#ifndef mm_sizeof
#define mm_sizeof _mmm_sizeof
#endif
#endif // mm_sizeof
/**
* MemMon element count
*/
#if MEMMON_TYPE==MAPMEMMON_TYPE
#ifndef mm_count
#define mm_count _mmm_count
#endif
#endif // mm_count
class MemMon
{
public:
virtual ~MemMon() = 0;
void Cleanup();
};
#endif // MEMMON_H
#include "SetMemMon.h"
using namespace std;
std::set<void *> * SetMemMon::allocatedMemory = 0;
void SetMemMon::Initialize()
{
if (allocatedMemory == 0)
{
allocatedMemory = new std::set<void *>();
}
}
void SetMemMon::Cleanup()
{
if (allocatedMemory != 0)
{
delete allocatedMemory;
allocatedMemory = 0;
}
}
bool SetMemMon::Null(void * address)
{
bool rc = (allocatedMemory == 0);
rc = (!rc ? allocatedMemory->find(address) == allocatedMemory->end() : rc);
return rc;
}
#ifndef SETMEMMON_H
#define SETMEMMON_H
#include <cstdlib>
#include <type_traits>
#include <set>
/**
* SetMemMon - Memory monitor using a stl set
*
* It is not recommended to use the macros and functions in this file directly.
* Instead, use MemMon.h and set the MEMMON_TYPE preprocessor definition to
* SETMEMMON_TYPE.
*/
/**
* SetMemMon new
*/
#ifndef _smm_new
#define _smm_new(constructor) SetMemMon::New<decltype(constructor)>(new constructor, 1)
#endif // _smm_new
/**
* SetMemMon new array
*/
#ifndef _smm_new_a
#define _smm_new_a(type, n) SetMemMon::New<type>(new type[n](), n)
#endif // _smm_new_a
/**
* SetMemMon delete
*/
#ifndef _smm_delete
#define _smm_delete(data) SetMemMon::Delete<std::remove_reference<decltype(*data)>::type>(data, false)
#endif // _smm_delete
/**
* SetMemMon delete array
*/
#ifndef _smm_delete_a
#define _smm_delete_a(data) SetMemMon::Delete<std::remove_reference<decltype(*data)>::type>(data, true)
#endif // _smm_delete_a
/**
* SetMemMon null check
*/
#ifndef _smm_null
#define _smm_null SetMemMon::Null
#endif // _smm_null
class SetMemMon
{
public:
virtual ~SetMemMon() = 0;
static void Initialize();
static void Cleanup();
template<typename T>
static T * New(T * data, size_t count);
template<typename T>
static void Delete(T * data, bool isArray);
static bool Null(void * address);
private:
static std::set<void *> * allocatedMemory;
};
template<typename T>
inline T * SetMemMon::New(T * data, size_t count)
{
if (allocatedMemory == 0)
{
Initialize();
}
if (allocatedMemory != 0 && data != 0 && count > 0)
{
allocatedMemory->insert((void *)data);
}
else
{
if (data != 0)
{
if (count > 1)
{
delete[] data;
}
else
{
delete data;
}
data = 0;
}
}
return data;
}
template<typename T>
inline void SetMemMon::Delete(T * data, bool isArray)
{
if (data != 0)
{
if (allocatedMemory != 0)
{
allocatedMemory->erase((void *)data);
if (allocatedMemory->empty())
{
Cleanup();
}
}
if (isArray)
{
delete[] data;
}
else
{
delete data;
}
}
}
#endif // SETMEMMON_H
#include <iostream>
#include <ctime>
#include "MemMon.h"
int main(int argc, char *argv[])
{
int rc = 0;
for (int i = 0; i < 10; ++i)
{
clock_t t_start = clock();
clock_t t_finish = t_start;
for (int i = 0; i < 100000; ++i)
{
int *foo = mm_new_a(int ,10000);
mm_delete_a(foo);
}
t_finish = clock() - t_start;
printf("%ld clicks (%f seconds)\n", t_finish, ((float)t_finish) / CLOCKS_PER_SEC);
printf("\n");
}
printf("Done. Press enter.");
getchar();
return rc;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment