Skip to content

Instantly share code, notes, and snippets.

@W4RH4WK
Last active January 1, 2025 14:49
Show Gist options
  • Save W4RH4WK/f8d32ad7a75968499060e9265879c25f to your computer and use it in GitHub Desktop.
Save W4RH4WK/f8d32ad7a75968499060e9265879c25f to your computer and use it in GitHub Desktop.
C++ Memory Arena (w/ destructor support)
#include <cassert>
#include <cstdint>
#include <cstdlib>
#include <iostream>
#include <type_traits>
using u8 = uint8_t;
using u32 = uint32_t;
using usize = size_t;
////////////////////////////////////////////////////////////
// Memory Arena
// A basic memory arena. Object destructors are invoked in reverse order.
//
// Internally, regular allocations are done from the front, while a stack of
// destructor records grows from the back.
struct Arena {
// Creates an arena with the given size.
explicit Arena(usize size) noexcept : base(reinterpret_cast<u8*>(malloc(size))), totalSize(size), malloced(true) {}
// Use the arena to manage the given block of memory.
explicit Arena(u8* base, usize size) noexcept : base(base), totalSize(size) {}
~Arena() noexcept
{
clear();
if (malloced) {
free(base);
}
}
Arena(const Arena&) = delete;
Arena& operator=(const Arena&) = delete;
Arena(const Arena&&) noexcept = delete;
Arena& operator=(const Arena&&) noexcept = delete;
// Allocates a block of bytes; returns nullptr on failure.
void* alloc(usize size) noexcept
{
if (size > bytesAvailable()) {
return nullptr;
}
u8* result = base + usedFront;
usedFront += size;
return static_cast<void*>(result);
}
// Type-erasing wrapper for an object's destructor.
struct Destructor {
using DtorFn = void(void*);
DtorFn* fn;
void* addr;
};
// Creates an object of the given type, forwarding all arguments to the
// constructor. If the object is not trivially destructable, a destructor is
// registered in the arena. Returns nullptr on failure.
template <typename T, typename... Args>
T* create(Args&&... args)
{
usize bytesNeeded = sizeof(T);
if constexpr (!std::is_trivially_destructible_v<T>) {
bytesNeeded += sizeof(Destructor);
}
if (bytesNeeded > bytesAvailable()) {
return nullptr;
}
T* object = new (base + usedFront) T(std::forward<Args>(args)...);
usedFront += sizeof(T);
if constexpr (!std::is_trivially_destructible_v<T>) {
destructorCount++;
auto* dtor = topDestructor();
dtor->fn = +[](void* object) noexcept { reinterpret_cast<T*>(object)->~T(); };
dtor->addr = object;
}
return object;
}
// Retrieves the top-most destructor slot.
Destructor* topDestructor() noexcept
{
if (destructorCount > 0) {
return reinterpret_cast<Destructor*>(base + totalSize - destructorCount * sizeof(Destructor));
} else {
return nullptr;
}
}
// Clears out the arena; invokes objects' destructors in reverse order.
void clear() noexcept
{
for (; destructorCount > 0; destructorCount--) {
auto* dtor = topDestructor();
(*dtor->fn)(dtor->addr);
}
usedFront = 0;
}
usize bytesUsed() const { return usedFront + destructorCount * sizeof(Destructor); }
usize bytesAvailable() const { return totalSize - bytesUsed(); }
u8* base = nullptr;
usize totalSize = 0;
usize usedFront = 0;
bool malloced = false;
u32 destructorCount = 0;
};
////////////////////////////////////////////////////////////
// Example usage
struct Foo {
Foo() = default;
~Foo() noexcept { std::cout << "Foo dtor\n"; }
};
struct Bar {
Bar(int id) : id(id) {}
~Bar() noexcept { std::cout << "Bar " << id << " dtor\n"; }
int id = 0;
};
int main()
{
Arena arena(512);
auto* data = arena.alloc(64);
Foo* foo = arena.create<Foo>();
Bar* bar1 = arena.create<Bar>(1);
arena.clear();
Bar* bar2 = arena.create<Bar>(2);
Bar* bar3 = arena.create<Bar>(3);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment