The Developer’s Cry

a blog about computer programming

Step Into The Arena (Principia)

Ten years ago (oh how time flies) I wrote on this blog about putting the fun back in C by leveraging C++. It was about giving C super powers by providing its most missed features, being dynamic arrays and hash maps. This time, we’re going to put the fun back in C by leveraging C. No, we’re not going emulate OOP in plain C, quite the opposite. This technique has a particular oldschool flavor about it. The arena allocator is a memory management technique that is a fundamental game changer in how to program in C.

Status quo

Memory management plays a large part in programming in C. In principle every malloc() must be paired with a free(). Sounds easy enough, but the risk of forgetting to free is real, especially in branchy code paths, and tracking down such a bug can be awfully hard.

The heavy use of malloc() and free() in C implicitly leads to data structures in the way that we were taught in CS class: linked lists and binary trees, built from pointers. Nobody told us back then about the cost: when memory fragments, performance goes down the drain. Chasing pointers means jumping through memory; cache misses stall the CPU for hundreds of cycles before it can continue to run. Walking a linked list through fragmented memory is a giant stutter fest from the CPU’s point of view.

The fix for this is to rely on plain-old-data arrays as much as you can. Accidentally, the arena allocator helps us doing just that.

Memoriae distributio

What is an arena? It’s a big chunk of memory, from which we will take a piece every time we allocate memory. When we allocate memory, we just “bump” the current pointer (hence the alternative name: bump allocator). A picture tells a thousand words:

The trick is, we will never free() individual allocations—we don’t want to write any complicated memory manager, that’s what malloc() is for. Rather than freeing individual allocs, we can only free the entire chunk all at once. Alternatively, we can reset the bump pointer and reuse the entire chunk.

Both allocating from the chunk and freeing memory by resetting the chunk are incredibly cheap operations in terms of performance. On top of that we get better CPU cache usage.

Implementation detail: the data must be aligned (to 64 bits on modern architectures). Unaligned memory access hurts performance, or may even crash the program with a bus error.

Carpe diem

You may have noticed that the arena is great for scratch buffers. You can make an arena at the beginning of a function, and then destroy the entire thing in one call at the end.

But arenas are not just for scratch. Most programs allocate memory at program start, and then keep it around for the entire lifetime of the program. Such data is a great candidate for keeping in a “global” arena. Other allocations fit an intermediate lifetime; most programs can get by with three or four distinct arenas.

It’s about choosing the right bucket for the lifetime of the data. In return you don’t get to worry about micro-managing individual allocations.

What if the arena runs out of memory? The easiest option is to simply panic with an out of memory error: it will teach you something about how the program actually behaves. Another option is to chainlink segments together. Obviously, the chunk size must be large enough to satisfy any allocation. [Note that the chunk size doesn’t necessarily have to be the same for all segments …].

Ordinatio dynamica

Speaking of program behavior, now is a good moment to talk about dynamic arrays. When a dynamic array runs out of elements, it automatically expands its space by reallocating (typically 2x) twice the capacity.

Iff the array is the last object in the arena, then it can easily expand. If it is not the last object in the arena however, there is no room to expand; the entire array must be relocated to newly allocated space.

What happens to the old space where the array was sitting previously? Nothing really, it stays in the arena, unused. It is dead, wasted space. The arena does not reclaim space (until the entire chunk is reset).

So when you’re pushing values onto an arena-backed dynamic array, you either want to reserve sufficient space upfront (always a good idea), or better yet, make sure that the array is the last object in the arena.

Only the last object in the arena can be reallocated (grow or shrink). The same holds true for deletion (ie. free), but note that arenas are about grouping allocations, not about micro-managing allocs and frees.

Opus magnum

Arenas are not a silver bullet for every situation. For example, if you’re doing lots of small entities that are frequently created and destroyed, then a pool allocator is a better choice.

In the year 2026 anno domini you should really think twice about writing new codes in C. If you insist, then it may be a good idea to design the program around arenas. When you go all-in on arenas, you will find it thrives in an ecosystem of its own.

Since arenas take up such a central position, it naturally becomes the fundament for a framework that ultimately replaces large swaths of the C standard library. Proper strings with length, dynamic arrays, hash maps, all built in pure C, on top of arenas. Implementing all that is quite an undertaking. It’s a quest that the most dedicated C programmers embark on, almost as a rite of passage.