A fast, simple, single-header arena/region allocator for C.
An arena allocator is a memory management strategy where you allocate a large block of memory upfront, then quickly hand out pieces of it by simply incrementing a pointer. When you're done, you free everything at once.
Initial State:
βββββββββββββββββββββββββββββββββββββββ
β Arena (4096 bytes) β
β βββββββββββββββββββββββββββββββββββ β
β β Empty Memory β β
β βββββββββββββββββββββββββββββββββββ β
β offset: 0 β
βββββββββββββββββββββββββββββββββββββββ
After Allocations:
βββββββββββββββββββββββββββββββββββββββ
β Arena (4096 bytes) β
β ββββββ¬ββββββ¬βββββββββ¬βββββββββββββββ β
β β A β B β C β Empty β β
β ββββββ΄ββββββ΄βββββββββ΄βββββββββββββββ β
β offset: 940 β
βββββββββββββββββββββββββββββββββββββββ
This arena uses a self-referential API pattern. Each arena contains a self pointer that references itself, and you call methods using:
arena->function(arena->self, parameters...)Arena* arena = Arena_create(4096);
β
βββββββββββββββββββββββββββ
β Arena struct β
β self βββ β
β alloc β β function β
β destroyβ pointers β
βββββββββββΌββββββββββββββββ
β
βββ Points to itself
int* data = arena->alloc(arena->self, sizeof(int) * 100);
arena->destroy(arena->self);
size_t mark = arena->get_mark(arena->self);
arena->reset_to_mark(arena->self, mark);The pattern is: arena->method_name(arena->self, other_params...)
int* a = malloc(sizeof(int) * 10); // System call
char* b = malloc(100); // System call
double* c = malloc(sizeof(double) * 5); // System call
free(a); // Track each allocation
free(b); // Easy to leak memory
free(c); // Manual bookkeepingArena* arena = Arena_create(4096); // One allocation
int* a = arena->alloc(arena->self, sizeof(int) * 10); // Pointer math
char* b = arena->alloc(arena->self, 100); // Pointer math
double* c = arena->alloc(arena->self, sizeof(double) * 5); // Pointer math
arena->destroy(arena->self); // Free everything at once| Operation | Arena | malloc/free |
|---|---|---|
| Allocation Speed | O(1) 2-3 cycles | O(log n) 100+ cycles |
| Per-alloc Overhead | 0 bytes | 8-16 bytes |
| Batch Free | O(1) | O(n) |
| Fragmentation | None | Possible |
| Cache Performance | Sequential | Random |
Copy arena.h to your project. Done.
In ONE .c file:
#define ARENA_IMPLEMENTATION
#include "arena.h"In all other files:
#include "arena.h"Arena* arena = Arena_create(4096);int* numbers = (int*)arena->alloc(arena->self, sizeof(int) * 100);
char* message = (char*)arena->alloc(arena->self, 256);for (int i = 0; i < 100; i++) {
numbers[i] = i * i;
}
sprintf(message, "Arena allocator works!");arena->destroy(arena->self);#define ARENA_IMPLEMENTATION
#include "arena.h"
int main(void) {
Arena* arena = Arena_create(4096);
int* data = (int*)arena->alloc(arena->self, sizeof(int) * 100);
for (int i = 0; i < 100; i++) {
data[i] = i * i;
}
printf("First: %d, Last: %d\n", data[0], data[99]);
arena->destroy(arena->self);
return 0;
}Compile:
gcc your_file.c -o program
./programArena* Arena_create(size_t size);Creates a new arena with the specified size in bytes.
arena->destroy(arena->self);Destroys the arena and frees all memory.
void* ptr = arena->alloc(arena->self, size_t size);Allocates size bytes from the arena. Returns pointer or NULL on failure.
Example: int* data = (int*)arena->alloc(arena->self, sizeof(int) * 100);
void* ptr = arena->alloc_aligned(arena->self, size_t size, size_t alignment);Allocates size bytes aligned to alignment boundary (must be power of 2).
Example: float* data = (float*)arena->alloc_aligned(arena->self, 1024, 16);
void* new_ptr = arena->realloc(arena->self, void* ptr, size_t old_size, size_t new_size);Reallocates memory. Tries to expand in place if possible.
Example: str = (char*)arena->realloc(arena->self, str, 10, 50);
arena->reset(arena->self);Resets all allocations. Memory becomes available again but is not freed.
size_t mark = arena->get_mark(arena->self);Returns current allocation offset (checkpoint).
Example: size_t checkpoint = arena->get_mark(arena->self);
arena->reset_to_mark(arena->self, size_t mark);Restores arena to a previously saved checkpoint.
Example: arena->reset_to_mark(arena->self, checkpoint);
bool success = arena->resize(arena->self, size_t new_size);Manually resizes the current arena chunk.
Example: if (arena->resize(arena->self, 4096)) { ... }
arena->print_stats(arena->self);Prints detailed memory usage statistics.
The arena uses "bump allocation" - the fastest allocation strategy:
void* ptr = arena->memory + arena->offset;
arena->offset += size;
return ptr;That's it. Just 2-3 CPU cycles.
Step 1: alloc(40 bytes)
βββββββββββββββββββββββββββββββββββββββ
β ββββββββ¬βββββββββββββββββββββββββββββ β
β β 40B β Empty Memory β β
β ββββββββ΄βββββββββββββββββββββββββββββ β
β offset: 40 β β
βββββββββββββββββββββββββββββββββββββββ
Step 2: alloc(100 bytes)
βββββββββββββββββββββββββββββββββββββββ
β ββββββββ¬βββββββββ¬ββββββββββββββββββββ β
β β 40B β 100B β Empty Memory β β
β ββββββββ΄βββββββββ΄ββββββββββββββββββββ β
β offset: 140 β β
βββββββββββββββββββββββββββββββββββββββ
Step 3: alloc(200 bytes)
βββββββββββββββββββββββββββββββββββββββ
β ββββββββ¬βββββββββ¬βββββββββ¬ββββββββββ β
β β 40B β 100B β 200B β Empty β β
β ββββββββ΄βββββββββ΄βββββββββ΄ββββββββββ β
β offset: 340 β β
βββββββββββββββββββββββββββββββββββββββ
When the arena runs out of space, it automatically creates a new chunk:
Arena Full:
βββββββββββββββββββββββββββββββββββββββ
β Chunk 1 (4096 bytes) - FULL β
β βββββββββββββββββββββββββββββββββββ β
β β βββββββββββββββββββββββββββββββββ β
β βββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββ
β Need more space
βββββββββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββββ
β Chunk 1 (4096 bytes) - FULL β βββ β Chunk 2 (8192 bytes) β
β βββββββββββββββββββββββββββββββββββ β β ββββββββββ¬βββββββββββββββββββββββ β
β β βββββββββββββββββββββββββββββββββ β β β Active β Empty β β
β βββββββββββββββββββββββββββββββββββ β β ββββββββββ΄βββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββββ
Save your position, do temporary work, then restore. Marks work across multiple chunks automatically.
State 1: Save Checkpoint
βββββββββββββββββββββββββββββββββββββββ
β βββββββββββ¬ββββββββββββββββββββββββββ β
β β Used β Empty β β
β βββββββββββ΄ββββββββββββββββββββββββββ β
β checkpoint = 500 β β
βββββββββββββββββββββββββββββββββββββββ
State 2: Temporary Allocations (Multi-Chunk)
βββββββββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββββ
β Chunk 1 β βββ β Chunk 2 β
β βββββββββββ¬ββββββββββββββββββββββββββ β β ββββββββββββ¬ββββββββββββββββββββββ β
β β Used β Temp in Chunk 1 β β β β Temp 2 β Empty β β
β βββββββββββ΄ββββββββββββββββββββββββββ β β ββββββββββββ΄ββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββββ
State 3: Restore to Checkpoint (Resets Both Chunks)
βββββββββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββββ
β Chunk 1 β βββ β Chunk 2 β
β βββββββββββ¬ββββββββββββββββββββββββββ β β βββββββββββββββββββββββββββββββββββ β
β β Used β Available Again! β β β β Reset to 0 β β
β βββββββββββ΄ββββββββββββββββββββββββββ β β βββββββββββββββββββββββββββββββββββ β
β offset: 500 β restored β β offset: 0 β
βββββββββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββββ
Note: Marks track absolute position across all chunks, so you can save a checkpoint in one chunk and reset from another chunk safely.
Arena* frame_arena = Arena_create(1024 * 1024);
while (game_running) {
Entity* entities = frame_arena->alloc(frame_arena->self, sizeof(Entity) * count);
Particle* particles = frame_arena->alloc(frame_arena->self, sizeof(Particle) * p_count);
render_frame(entities, particles);
frame_arena->reset(frame_arena->self);
}
frame_arena->destroy(frame_arena->self);Arena* scratch = Arena_create(4096);
void process_data(void) {
size_t checkpoint = scratch->get_mark(scratch->self);
char* temp_buffer = scratch->alloc(scratch->self, 2048);
int* temp_array = scratch->alloc(scratch->self, sizeof(int) * 1000);
scratch->reset_to_mark(scratch->self, checkpoint);
}Arena* string_arena = Arena_create(4096);
char* build_path(Arena* a, const char* dir, const char* file) {
size_t len = strlen(dir) + strlen(file) + 2;
char* path = a->alloc(a->self, len);
sprintf(path, "%s/%s", dir, file);
return path;
}
char* path1 = build_path(string_arena, "/usr", "bin");
char* path2 = build_path(string_arena, "/home", "user");
string_arena->destroy(string_arena->self);Arena* simd_arena = Arena_create(8192);
float* vectors = (float*)simd_arena->alloc_aligned(simd_arena->self,
sizeof(float) * 1024,
16);
simd_arena->destroy(simd_arena->self);- Game frame allocations
- Parsers and compilers (AST nodes)
- Loading data files
- String manipulation
- Temporary buffers
- Batch processing
- Long-lived objects with different lifetimes
- When you need to free individual items
- Thread-shared memory without synchronization
malloc(40) βββ [Block A] System call
malloc(100) βββ [Block B] System call
malloc(200) βββ [Block C] System call
free(A) βββ Track each System call
free(B) βββ allocation System call
free(C) βββ manually System call
Arena_create(4096) βββ [Large Block] One system call
arena->alloc(arena->self, 40) βββ [A|B|C|Empty] Just pointer math
arena->alloc(arena->self, 100) βββ [A|B|C|Empty] Just pointer math
arena->alloc(arena->self, 200) βββ [A|B|C|Empty] Just pointer math
arena->destroy(arena->self) βββ Free all One call
#define ARENA_IMPLEMENTATION
#include "arena.h"
int main(void) {
Arena* arena = Arena_create(4096);
process_data(arena);
arena->destroy(arena->self);
return 0;
}#include "arena.h"
void process_data(Arena* arena) {
int* data = arena->alloc(arena->self, sizeof(int) * 100);
}#include "arena.h"
void process_data(Arena* arena);Not thread-safe by default. Use one arena per thread:
_Thread_local Arena* thread_arena = NULL;
void thread_init(void) {
thread_arena = Arena_create(64 * 1024);
}
void thread_cleanup(void) {
thread_arena->destroy(thread_arena->self);
}Typical performance on modern hardware:
| Metric | Arena | malloc |
|---|---|---|
| Allocation | 2-3 cycles | 100+ cycles |
| 1000 allocations | ~0.001ms | ~0.1ms |
| Overhead per alloc | 0 bytes | 8-16 bytes |
| Free all | O(1) | O(n) |
-
Pre-allocate if you know the size
Arena* arena = Arena_create(known_total_size);
-
Use checkpoints for nested operations
size_t mark = arena->get_mark(arena->self); arena->reset_to_mark(arena->self, mark);
-
Reset instead of destroy for repeated operations
for (int i = 0; i < iterations; i++) { arena->reset(arena->self); }
-
Monitor usage during development
arena->print_stats(arena->self);
-
Use aligned allocations for SIMD
float* data = arena->alloc_aligned(arena->self, size, 16);
One .c file must have #define ARENA_IMPLEMENTATION before including.
Only ONE file should have #define ARENA_IMPLEMENTATION.
Increase initial size or use reset to reuse memory.
See example.c for comprehensive examples covering all features.
Compile:
gcc example.c -o example
./exampleOr:
clang example.c -o example
./examplePublic domain. Use however you want.