Reputation:
Um, hello. I'm trying to write a custom stack memory allocator to help with my game programming, and have run into an issue. So, let's say my allocator has a char* buffer, and I want to get some memory for an int:
class MemoryStack
{
public:
MemoryStack(unsigned size)
{
mSize=size;
mTop=0;
mBuffer = new(std::nothrow) char [size];
}
char* allocate(unsigned size)
{
if (mTop + size > mSize)
return nullptr;
char* out = mBuffer+mTop;
mTop+=size;
return out;
}
private:
char* mBuffer;
unsigned mTop;
unsigned mSize;
};
int main ()
{
MemoryStack stack(1024);
int testval = 6;
int* ptr = (int*)stack.allocate(sizeof(int));
*ptr = testval;
std::cout<<*ptr;
}
Now, this works, and prints out 6. However, when I try something like:
int main ()
{
MemoryStack stack(1024);
std::string str = "HELLO :p";
std::string* strptr = (std::string*)stack.allocate(sizeof(std::string));
*strptr = str ;
std::cout<<*strptr;
}
...This gives me a Bad Ptr problem, and a segmentation fault, crashing my program. Can anyone explain why this is? Could this be because of some operator = overloading? And would there be any way to safely handle this? Thanks!
Edit: The following code is my currently final implementation after the help I received - it's not thoroughly tested, but seems to work as intended. I'll naturally modify it if any bugs are found, but as to help out anyone interested, here it is ^_^ Feel free to use it if you wish, although it's hardly the summit of computer science.
class MemoryStack;
/**Serves as a bookmark for the memory stack in order to allow to clear only part of the memory.*/
class MemoryBookmark
{
private:
/**Private constructor may only be called by the memory stack object.*/
MemoryBookmark(unsigned value)
{
mBookmark = value;
}
unsigned mBookmark;
public:
friend class MemoryStack;
/**Returns the index of the position that will be the new stack top pointer.*/
decltype(mBookmark) getValue() const
{
return mBookmark;
}
};
/**Acts as a basic memory stack to help reduce allocation costs, as well as add to the fun! Use with care, as destructors must be called manually.*/
class MemoryStack
{
private:
char* mBuffer;
size_t mTop;
size_t mCapacity;
size_t mAlignment;
public:
/**Initialises the class, reserving _capacity_ bytes for use. It can not be resized for efficiency purposes.*/
MemoryStack(unsigned capacity)
{
mCapacity = capacity;
mTop = 0;
mBuffer = new(std::nothrow) char[capacity];
mAlignment = 4;
}
/**Frees the memory, invalidating all internal memory. Doesn't call destructors.*/
~MemoryStack()
{
if (mBuffer)
delete[] mBuffer;
}
/**Creates an instance of the given type with Args if possible, using aligned internal memory.*/
template <typename T, typename... Args>
void create(T*& ptr, Args&&... args)
{
ptr = (T*)allocate(sizeof(T));
if (!ptr)
return;
else
new (ptr)T(std::forward<Args>(args)...);
}
/**Calls the destructor of the pointer. Must be used if destruction important.*/
template<typename T>
void destroy(T* ptr)
{
ptr->~T();
}
/**Allocates a piece of memory for use.*/
void* allocate(size_t amount)
{
size_t bt = (size_t)(mBuffer + mTop);
size_t alignoffset = mAlignment - (bt & (mAlignment - 1));
alignoffset = alignoffset == mAlignment ? 0 : alignoffset;
size_t size = amount + alignoffset;
if (size + mTop > mCapacity)
return nullptr;
else
{
mTop += size;
return (void*)(bt + alignoffset);
}
}
/**Returns the amount of memory used.*/
size_t size() const
{
return mTop;
}
/**Returns the size of the memory reserved for use.*/
size_t capacity() const
{
return mCapacity;
}
/**Returns the number of bytes remaining for allocation.*/
size_t remaining() const
{
return mCapacity - mTop;
}
/**Checks whether the internal memory was allocated successfully.*/
bool isValid() const
{
return mBuffer != nullptr;
}
/**Creates a 'bookmark' which can be used to clear the stack until a given point.*/
MemoryBookmark createBookmark() const
{
return MemoryBookmark(mTop);
}
/**Resets the stack. All data inside may now be overwritten. Doesn't call destructors.*/
void reset()
{
mTop = 0;
}
/**Resets the stack up to a given bookmark. Again, no destructors called!*/
void resetToBookmark(const MemoryBookmark bookmark)
{
mTop = bookmark.getValue();
}
/**Sets the alignment of the reservations in memory.*/
void setAlignment(size_t alignment)
{
mAlignment = alignment;
}
/**Returns the currently used alignment.*/
decltype(mAlignment) getAlignment() const
{
return mAlignment;
}
};
/**Test class.*/
class Test
{
public:
Test(int val)
{
v = val;
std::cerr << "Constructor\n";
}
~Test()
{
std::cerr << "Destructor";
}
int v;
};
/**Test it! XD*/
int main()
{
using namespace std;
{
MemoryStack stack(4096);
Test* test=nullptr;
int* i1, *i2;
char* c1, *c2;
stack.create(test,3);
stack.create(i1, 2);
stack.create(c1, 'a');
stack.create(i2, 3);
stack.create(c2, 'm');
stack.destroy(test);
stack.reset();
}
cin.get();
}
Upvotes: 0
Views: 328
Reputation: 44073
An uninitialized region of memory does not a std::string
make. You have to construct an object into the region with placement new like so:
std::string *strptr = (std::string*) stack.allocate(sizeof(std::string));
new (strptr) std::string;
It is worth thinking about doing this in a central place, such as a member function template in MemoryStack
.
EDIT:
I forgot to mention this earlier, which is a pity because it is bloody important:
If you construct objects with placement new, you have to destruct them manually as well. They're not going to do it themselves because they don't have automatic storage duration, and you can't use delete
because the memory wasn't allocated with new
. The syntax is pretty straightforward:
strptr->~string();
It would be a good idea to make both parts of this a part of MemoryStack
, for example like this:
class MemoryStack {
...
template<typename T, typename... Args>
T *create(Args&&... args) {
T *ptr = allocate(sizeof(T));
try {
new(ptr) T(std::forward<Args>(args)...);
} catch(...) {
deallocate(ptr);
throw;
}
return ptr;
}
template<typename T>
void destroy(T *ptr) {
ptr->~T();
deallocate(ptr);
}
...
};
to later write
std::string *strptr = stack.create<std::string>("foo");
...
stack.destroy(strptr);
or so. Pro tip: Build yourself a deleter you can use in conjunction with std::unique_ptr
and std::shared_ptr
to make exception safety easy.
Upvotes: 1
Reputation: 41
you are trying to allocate size of std::string std::string* strptr = (std::string*)stack.allocate(sizeof(std::string));
you probably meant something like std::string* strptr = (std::string*)stack.allocate(str.size()));
Wintermute's is a nice idea but it probably wont do what you want a. your allocate function will return nullptr b. the "new" will allocate a new memory area, not in your "heap"
probably won't crash though...
Draknghar: (i'm to young to add comments) changed my mind. it will work. but you didn't set mTop... something seems wrong in doing it that way. you already have you array and your trying to allocate new memory in it? why? just copy your string into it, and set mTop.
Upvotes: 0