A Small Memory Arena in C
What is a memory arena?
An arena is a large, fixed region of memory that's allocated once, up front. Instead of asking the OS for memory on every allocation, the program just moves a cursor through that region as requests come in. Freeing gets simpler as well: the whole batch of allocations is released at once, rather than freeing each individually.
It can be compared to how function call memory works: a function creates a stack frame where its local variables live, and that whole frame is destroyed at once when the function returns.
Where it's useful
Arenas are useful when there is a clear lifetime for a given workload, e.g. handling a single
request, a parser pass over each file, a single game frame. Everything allocated during that pass
shares the same lifetime, so it can be allocated and freed without the overhead of each
malloc/free.
Code example
arena.cSimple arena implementationHow this implementation works
The example reserves a 1 MiB region with mmap. Arena stores the beginning, current cursor, and
end of that region. Allocation is nothing more than a bounds check and a pointer bump:
arena_alloc hands back the current cursor and moves it forward by size.
There's no per-allocation header - nothing needs to know an individual block's size later, because
nothing ever walks back through the region. That's what keeps this simple: no free-list search, no
metadata, no bookkeeping beyond the three pointers in Arena.
Freeing is just as simple, and it only comes in one shape: release everything at once. arena_reset
moves cursor back to start, so the whole region is immediately available for reuse. When the
arena itself is no longer needed, munmap returns the memory to the OS. There is no
arena_free(ptr) for a single allocation - that operation doesn't exist here, on purpose. Needing it
means reaching for a different allocator, not extending this one.
This is intentionally a learning implementation rather than a production allocator. It does not
handle alignment, overflow, thread safety, or all mmap/munmap failure details.
Memory layout
Tracing the main example - six allocations of different sizes (a through f, from 48 to 512
bytes) - the mmap'd region looks like this. Each allocation is placed back to back with no gap and
no header, and cursor marks the boundary between used and untouched memory. start and end
never move - they're the boundaries of the single mmap call; cursor is the only thing that
advances, one allocation at a time.
After arena_reset(), the picture changes:
A single call moves cursor back to start. All six allocations disappear at once - there's no
free-list to update, no coalescing, nothing to walk. The bytes are just as unreachable as before
a was ever allocated, and the arena is ready to be reused from scratch.
Arena pros
- Allocation is cheap. No free-list search, no syscall per call - just a bounds check and a pointer bump.
- Cache locality. Allocations from the same batch sit right next to each other in memory, so walking through them later tends to stay in cache instead of jumping around.
- Freeing is O(1). Dropping the whole batch is one cursor reset (or
munmap), instead of walking every allocation and callingfreeon each.
Arena cons
- No individual reclaim. An arena can't free one object early while others stay alive - that needs a different allocator (e.g. a free-list) built on top, which brings back general-purpose allocator complexity.
- Memory stays reserved for the arena's whole lifetime. Nothing returns to the OS until the whole arena is reset or destroyed, even if just one allocation is still "logically" alive.
- The size is a guess made upfront. Too little and the arena runs out mid-batch; too much and memory sits unused the whole time.
- Mixing lifetimes defeats the model. Arenas work best when everything allocated together also dies together - mix in long-lived objects and either they get held too long, or individual freeing creeps back in.