Reputation: 31
(first thing, I'm french so sorry for the typos) I'm currently in the process of learning memory optimisation for C++ programs. I discovered the concept of Arena Allocator and tried putting one together by myself. The allocator works fine for standard types or classes without inheritance. But as soon as I try to put a derivated class in the allocater and using them, I get a Segmentation fault.
On this exemple, the SIGSEGV is raised when trying to use the "third" pointer.
So what am I doing wrong and how to fix it? (I'm using cpp14 and Code::Blocks with default compiler [GNU GCC])
main.cpp
#include "util/arena.hpp"
class Foo
{
protected:
int m_val;
public:
Foo(int val): m_val(val) {}
Foo():m_val(0) {}
virtual ~Foo() {}
virtual int evaluate() {return m_val;}
};
class Bar: public Foo
{
protected:
Foo* m_other;
public:
Bar(int val, Foo* other): m_other(other) {m_val=val;}
Bar(): m_other(nullptr) {m_val=0;}
virtual ~Bar() {}
virtual int evaluate() {return m_val + m_other->evaluate();}
};
int main()
{
ArenaAllocator alloc(64);
Foo* first = alloc.allocate_type<Foo>();
Bar* second = alloc.allocate_type<Bar>();
Bar* third = alloc.allocate_type<Bar>();
*first = Foo(1);
*second = Bar(2,first);
*third = Bar(3,second);
third->evaluate(); /// <-- HERE
}
util/arena.hpp
#ifndef ARENA_HPP_INCLUDED
#define ARENA_HPP_INCLUDED
#include <stdexcept>
class ArenaAllocator{
typedef unsigned char uchar;
protected:
size_t m_size;
uchar* m_allocator; //uses char allocation to allow pointer arithmetic
uchar* m_current; //(forbidden for void pointers)
public:
/** \brief by default, the arena allocated has no data;
*/
ArenaAllocator();
/** \brief But you can specify that size using this constructor
*/
ArenaAllocator(size_t toAllocate);
/** \brief When the destructor is called, all data stored is deleted.
*/
virtual ~ArenaAllocator();
/** \brief Reserve a place in the arena
* \param toReserve: the size that the pointer should move
* returns a void pointer to the beginning of the reserved memory
*/
void* allocate(size_t toReserve);
/** \brief return a specific type of pointer with corresponding size of allocation
* it serves as a practical shortcut to allocate(), without specifying size of allocation
* or making type conversion
* \param number: lets you allocate multiple instances of the corresponding type. (like an array with number element)
*/
template<class T>
inline T* allocate_type(size_t number=1){return (T*)this->allocate(sizeof(T)*number);}
/** \brief Moves back the top of the arena. Does not destroy data, but it can be overridden by future allocation
* \param toScrap: the size that the pointer should move
*/
void backtrack(size_t toScrap);
/** \brief totally resets the arena. The allocation is still in place (and all data is kept)
* but the allocation pointer is thrown back at the beginning, which mean all allocated pointer
* are therefore "invalid", since data can be overwritten
*/
void clear();
/** \brief free all data and reallocates a new segment in memory;
*/
void reallocate(size_t toAllocate);
/** \brief returns the size of the arena
*/
const size_t size() const;
/** \brief returns a void pointer on the beginning of allocated bloc of memory
*/
const void* begin() const;
};
#endif // ARENA_HPP_INCLUDED
util/arena.cpp
#include "arena.hpp"
ArenaAllocator::ArenaAllocator(): m_size(0), m_allocator(nullptr), m_current(nullptr) {
}
ArenaAllocator::ArenaAllocator(size_t toAllocate): m_size(toAllocate){
m_allocator = new uchar[m_size];
m_current = m_allocator;
}
ArenaAllocator::~ArenaAllocator(){
if (m_allocator){
delete[] m_allocator;
}
}
void* ArenaAllocator::allocate(size_t toReserve){
void* allocated = m_current; //casting char pointer to void pointer so that conversion in output is easier
m_current += toReserve;
if (m_current>m_allocator+m_size){
throw std::bad_alloc();
}
return allocated;
}
void ArenaAllocator::clear(){
m_current = m_allocator;
}
void ArenaAllocator::backtrack(size_t toScrap){
m_current = std::max(m_allocator, m_current-toScrap);
}
void ArenaAllocator::reallocate(size_t toAllocate){
if (m_allocator){
delete[] m_allocator;
}
m_size = toAllocate;
m_allocator = new uchar[m_size];
m_current = m_allocator;
}
const size_t ArenaAllocator::size() const{
return m_size;
}
const void* ArenaAllocator::begin() const{
return m_allocator;
}
I tried the allocator with standard types, pointers, and class with pointers and it works just fine. But as soon as I use non-base classes with virtual function members it is screwing up. I used GDB to watch data inside the allocator and pointers are valid. Objects are also correctly constructed, so I suspect it is all about the objects' vtable;
Upvotes: 1
Views: 494
Reputation: 120049
A mindless, totally mechanical application of an essential tool immediately reveals the problem. The thing you think is a Foo
object isn't.
Why is that?
Because of this:
`inline T* allocate_type(size_t number=1){return (T*)this->allocate(sizeof(T)*number);}`
This function does not create a T
object. It allocates raw bytes, and cast their address to T*
. This does not make a T
magically appear there. Any dereference of the resulting pointer is undefined behaviour.
You need to invoke a constructor. A constructor is invoked by operator new
. In this case, you need placement new.
Upvotes: 1