2025-06-20
Arena allocators are great fun, they simplify the concerns over
lifetimes of objects-that-go-together. Perhaps morbidly stated as the
maxim “things that live together die together” arenas are objects that
help you manage allocations of objects, all while minimizing calls to
malloc
and trying to keep objects contiguous in memory.
In this post I want to provide a minimal implementation of arenas with some bells and whistles added for flare.
An arena is, at its core, a linked list of memory regions (why it’s also called region-based allocator) that are filled in monotonically, ie. from first to last until you are forced to add a new region.
The interface for an arena is quite simple, it lets you
arena_allocate(arena, T)
, giving you a T*
back; and you can arena_clear(arena)
which clears its usage
but doesn’t relinquish the memory back to the operating system; and you
can arena_free(arena)
, which clears and frees the memory
proper, setting the arena to it’s zero-initalized empty state.
struct arena {
struct arena* next;
void* buffer;
size_t capacity, offset;
};
#define arena_allocate(arena, T) \
((typeof(T))*) arena_allocate_pro((arena), sizeof(T), alignof(typeof(T)))
void* arena_allocate_pro(struct arena*, size_t sz, size_t al);
void arena_clear(struct arena*);
void arena_free(struct arena*);
An important detail that is often left out, and that tends to irk me quite a lot is the fact that alignment is important, so our “pro” allocation returns an aligned pointer as opposed to tightly fitting the elements.
static void* align_pointer_to(void* ptr, size_t alignment) {
return (void*) (((uintptr_t) ptr + alignment - 1) & ~(alignment - 1));
}
static struct arena* last_region(struct arena* arena) {
while (arena->offset == arena->capacity) {
if (arena->next == NULL)
return arena;
else
= arena->next;
arena }
return arena;
}
static bool region_can_fit(struct arena const* arena, size_t req) {
return arena->offset + req < arena->capacity;
}
static struct arena* new_region(struct arena* arena, size_t cap) {
(arena->next == NULL);
assert*(arena->next = calloc(1, sizeof(*arena->next))) = (struct arena) {
.buffer = malloc(cap),
.capacity = cap
};
return arena->next;
}
static size_t region_size(size_t min) {
static size_t const page_size = 1 << 14;
if (min <= page_size)
return page_size;
size_t capacity = page_size;
while (capacity < min)
= capacity << 1;
capacity return capacity;
}
void* arena_allocate_pro(struct arena* arena, size_t size, size_t align) {
:
retry= last_region(arena);
arena void* ptr = arena->buffer + arena_offset;
void* aligned = align_pointer_to(ptr, align);
size_t padding = aligned - ptr;
size_t used = padding + size;
if (region_can_fit(arena, used)) {
->offset += used;
arena(aligned, 0, size);
memsetreturn aligned;
} else {
->offset = arena->capacity;
arena(arena, region_size(size));
new_regiongoto retry;
}
}
void arena_clear(struct arena* arena) {
while (arena) {
->offset = 0;
arena= arena->next;
arena }
}
void arena_free(struct arena* arena) {
struct arena* dying = arena->next;
(arena->buffer);
free*arena = (struct arena){0};
while (dying) {
(dying->buffer);
freestruct arena* next = dying->next;
(dying);
free= next;
dying }
}
char* arena_strndup(struct arena* arena, char const* str, size_t n) {
size_t len = n;
for (size_t i = 0; i < len; i++)
if (str[i] == '\0') len = i;
char *buffer = arena_allocate_pro(arena, len + 1, alignof(char));
if (!buffer)
return NULL;
(buffer, str, len);
memcpy[len] = '\0';
bufferreturn buffer;
}
char* arena_strdup(struct arena* arena, char const* str) {
return arena_strndup(arena, str, strlen(str));
}
char* arena_format(struct arena*, char const* fmt, ...) {
va_list ap;
(ap, fmt);
va_startsize_t size = vsnprintf(NULL, 0, fmt, ap);
(ap);
va_end
char* buffer = arena_allocate(arena, size + 1, alignof(char));
if (!buffer)
return NULL;
(ap, fmt);
va_start(buffer, fmt, ap);
vsprintf(ap);
va_end
return buffer;
}
#define arena_copy(ar, p) \
typeof((p)) arena_copy_pro((ar), (p), sizeof(*(p)), alignof(*(p)))
void* arena_copy_pro(struct arena* arena, void* ptr, size_t sz, size_t al) {
void* buffer = arena_allocate_pro(arena, sz, al);
(buffer, ptr, sz);
memcpyreturn buffer;
}
Once again, you are invited to read the header-only library and example file.