Last active
January 1, 2025 14:49
-
-
Save W4RH4WK/f8d32ad7a75968499060e9265879c25f to your computer and use it in GitHub Desktop.
C++ Memory Arena (w/ destructor support)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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