0

I am writing a custom memory manager in C++ to monitor "new" and "delete" usage.

In my code, I have overloaded new, new[], delete, delete[] operators and am using a STL map to hold the addresses of the allocated memory as key values and storing the number of bytes allocated as mapped values. I also update byte_counter and number_of_allocations.

I keep receiving the error "Thread 1: EXC_BAD_ACCESS (code=2, address=0x7fff5f3fffd8)" (I am using XCode as my IDE) that takes me to this piece of code:

template <class _Tp, class _Compare, class _Allocator>
template <class ..._Args>
typename __tree<_Tp, _Compare, _Allocator>::__node_holder
__tree<_Tp, _Compare, _Allocator>::__construct_node(_Args&& ...__args)
{
    __node_allocator& __na = __node_alloc();
    __node_holder __h(__node_traits::allocate(__na, 1), _Dp(__na));
    __node_traits::construct(__na, _VSTD::addressof(__h->__value_), _VSTD::forward<_Args>(__args)...);
    __h.get_deleter().__value_constructed = true;
    return __h;
}

Here is my code:

#include <iostream>
#include <map>
#include <cstdlib>

using namespace std;

/********************************************************************************/

void* operator new (size_t st);
void* operator new [](size_t st);
void operator delete(void* p);
void operator delete [](void* p);

/********************************************************************************/

class DynamicMemoryManager
{
private:
    static int number_of_allocations;
    static size_t total_bytes;
    static map<const void*, const size_t> mem_map;

public:
    DynamicMemoryManager();

    static DynamicMemoryManager* create();
    static void destroy();

    static void print();
    static void add(const void* p, const size_t size);
    static void remove(const void* p);
};

int DynamicMemoryManager::number_of_allocations = 0;
size_t DynamicMemoryManager::total_bytes = 0;
map<const void*, const size_t> DynamicMemoryManager::mem_map;

DynamicMemoryManager::DynamicMemoryManager() {
    number_of_allocations = 0;
    total_bytes = 0;
    mem_map.clear();
}

void DynamicMemoryManager::print() {
    cout << "number_of_allocations: " << number_of_allocations << endl;
    cout << "total_bytes: " << total_bytes << endl;
}

void DynamicMemoryManager::add(const void* p, const size_t size) {
    number_of_allocations++;
    total_bytes += size;
    mem_map.insert(pair<const void*, const size_t>(p, size));
}

void DynamicMemoryManager::remove(const void*p) {
    size_t sz = mem_map[p];
    number_of_allocations--;
    total_bytes -= sz;
    mem_map.erase(p);
}

/********************************************************************************/

int main()
{
    DynamicMemoryManager::print();

    int* i = new int(88);
    double* d = new double(8.8);
    string* s = new string("8888");
    char* c = new char('8');

    DynamicMemoryManager::print();

    delete i;
    delete d;
    delete s;
    delete c;

    return 0;
}

/********************************************************************************/

void* operator new (size_t st) {
    void* p = malloc(st);
    DynamicMemoryManager::add(p, st);

    return p;
}

void* operator new [](size_t st) {
    void* p = malloc(st);
    DynamicMemoryManager::add(p, st);

    return p;
}

void operator delete(void* p) {
    free(p);
    DynamicMemoryManager::remove(p);
}

void operator delete [](void* p) {
    free(p);
    DynamicMemoryManager::remove(p);
}

I am not sure why my code keeps coming up with BAD_ACCESS errors. How should I design my code so that it successfully updates the STL map with each NEW and DELETE use? Please help, and thank you!

EDIT: I found that the error is coming from my overloaded NEW operator infinitely recursing. My professor hints that we should refer to void example5() in this piece of code. I'm still not sure exactly what I'm missing in my code to prevent my overloaded NEW from recursing :

#include <array>
#include <map>
#include <functional>
#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <regex>
#include <chrono>
#include <ctime>
#include <vector>
#include <memory>

using namespace std::chrono;
using namespace std;

array<string, 12> smonths =
{
    "january",
    "february",
    "march",
    "april",
    "may",
    "june",
    "july",
    "august",
    "september",
    "october",
    "november",
    "december"
};

vector<string> Parse(string text, string split)
{
    vector<string> words;
    sregex_token_iterator end;
    regex pattern(split);
    for (sregex_token_iterator pos(text.begin(), text.end(), pattern); pos != end; ++pos)
    {
        if ((*pos).length() > 0)
        {
            if ((static_cast<string>(*pos))[0] != 0x20)
                words.push_back(*pos);
        }
    }
    return words;
}

int getHash(string s)
{
    hash<string> hstring;
    return hstring(s);
}

struct KeyValue : pair<string, int>
{
    string key;
    int value;
    KeyValue(string s, int n) { first = s; second = n; }
    KeyValue(const KeyValue& kv) { first = kv.first; second = kv.second; }
    KeyValue(const KeyValue&& kv) { first = kv.first; second = kv.second; }
};

bool operator == (const KeyValue a, const KeyValue b)
{
    return  a.first.compare(b.first) == 0;
}

/*

If you think about how you usually allocate memory dynamically 
(using the 'new' operator), you could ask why the STL provides 
such a thing called allocator that does all the memory management 
of the container classes. The concept of allocators was originally 
introduced to provide an abstraction for different memory models 
to handle the problem of having different pointer types on certain 
16-bit operating systems (such as near, far, and so forth). 
However, this approach failed. Nowadays, allocators serve as an 
abstraction to translate the need to use memory into a raw call 
for memory.  Allocators simply separate the implementation of 
containers, which need to allocate memory dynamically, from the 
details of the underlying physical memory management. You can simply 
apply different memory models such as shared memory, garbage 
collections, and so forth to your containers without any difficulty 
because allocators provide a common interface.

*/

template <typename T>
class MallocAllocator
{
public:
    typedef T value_type;
    MallocAllocator() {}
    template <typename U> MallocAllocator(const MallocAllocator<U>& other) {}
    T* allocate(size_t count)
    {
        return (T*)malloc(count * sizeof(T));
    }
    void deallocate(T* object, size_t n)
    {
        void* ptr = reinterpret_cast<void*>(object);
        free(ptr);
    }
};

MallocAllocator<void*> memoryManager;

void* operator new(size_t size)
{
    //cout << "Allocating memory..." << endl;
    auto newObject = memoryManager.allocate(size);
    return newObject;
}

void operator delete(void* objectPtr) noexcept 
{
    void** ptr = reinterpret_cast<void**>(objectPtr);
    memoryManager.deallocate(ptr, 0);
    //free(objectPtr);
}

template <typename _Type = void>
struct Less
{   // functor for operator<
    constexpr bool operator()(const _Type& _Left, const _Type& _Right) const
    {   
        return (_Left < _Right);
    }
};

void example5()
{
    int* p = new int;
    system_clock::time_point tbegin = system_clock::now();
    map<string, int, Less<string>, MallocAllocator<int>> frequency;
    ifstream infile("Words.txt");
    while (!infile.eof())
    {
        string buffer;
        getline(infile, buffer);
        frequency[buffer] = 0;
    }
    infile.close();
    infile.open("Speech.txt");
    while (!infile.eof())
    {
        string buffer;
        getline(infile, buffer);
        vector<string> vs = Parse(buffer, "[a-zA-Z0-9]*");
        for (string s : vs)
        {
            int& number = frequency[s];
            ++number;
        }
    }
    ofstream outfile("Frequency.txt");
    for (auto p : frequency)
    {
        if (p.second)
        {
            outfile << p.first << "\t" << p.second << endl;
            cout << p.first << "\t" << p.second << endl;
        }
    }
    outfile.close();
    system_clock::time_point tend = system_clock::now();
    cout << "Duration: " << static_cast<double>((tend - tbegin).count()) / 10000000.0 << endl;
}
Steven Jing
  • 1
  • 1
  • 4
  • 1
    You do realize that `std::map` itself uses `new`/`delete` itself internally, as part of managing the contents of the map, which appears to invoke your overloaded operators, right? – Sam Varshavchik May 14 '16 at 03:02

1 Answers1

0

How should I design my code so that it successfully updates the STL map with each NEW and DELETE use?

Not easily. The methods of std::map<Key,T,Compare,Allocator> allocate and deallocate dynamic memory, to store and discard elements of the map.

They allocate and deallocate dynamic memory using the corresponding member functions of Allocator. By default Allocator is std::allocator<std::pair<const Key, T>> std::allocator allocates by calling the global operator new and deallocates by calling the global operator delete (that is, the operators you are replacing with your monitored versions, which insert and delete elements of your std::map).

Consequently, when your operator new, for instance, gets down to inserting the first element in the map, it gets called again in the insertion operation, which starts another insertion operation, and so on until you run out of memory and crash (as you have seen).

The only way to avoid this is to make your map an std::map<Key,T,Compare,CustomAllocator>, where CustomAllocator is an allocator you have written yourself that allocates and deallocates dynamic memory without calling ::operator new or ::operator delete.

You might attempt to do that by using the Standard C Library's malloc and free.

But I'd urge you not to bother. If you supposed that your memory manager would reveal all about the heap, the way in which you're going to have to write it is enough to show you that it won't. It won't know anything about it's own heap usage, nor about the heap usage of anything else linked in the program that handles heap through the malloc/free interface. To see what happens with the heap, the solution is Valgrind For OS X, see Yosemite and Valgrind

Community
  • 1
  • 1
Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182
  • Thank you for the suggestion, Mike!! This program is for a homework assignment - my professor hinted to look into void example5() in the above code (I just added it to my question), but I'm unsure as to what he's refering to exacxtly. I'm going to try to write a CustomAllocator and see if that works. – Steven Jing May 14 '16 at 21:51