27. Capstone: a bump/arena allocator
Real programs that allocate thousands of short-lived objects (parsers,
per-request web-server state, game-engine per-frame allocations) often
skip malloc/free entirely per-object and use an arena: allocate
one big block up front, hand out slices of it by just bumping a pointer,
and free the entire arena at once when the whole batch of work is done.
// arena.h
#ifndef ARENA_H
#define ARENA_H
#include <stddef.h>
typedef struct {
char *base;
size_t size;
size_t offset;
} Arena;
Arena arena_new(size_t size);
void *arena_alloc(Arena *a, size_t size);
void arena_reset(Arena *a); // "free everything" — just rewind the pointer
void arena_destroy(Arena *a);
#endif
// arena.c
#include "arena.h"
#include <stdlib.h>
#include <stdint.h>
Arena arena_new(size_t size) {
return (Arena){ .base = malloc(size), .size = size, .offset = 0 };
}
void *arena_alloc(Arena *a, size_t size) {
if (!a->base || a->offset > SIZE_MAX - 7) return NULL;
size_t aligned = (a->offset + 7) & ~(size_t)7; // 8-byte align every allocation
if (aligned > a->size || size > a->size - aligned) return NULL; // out of arena space
void *ptr = a->base + aligned;
a->offset = aligned + size;
return ptr;
}
void arena_reset(Arena *a) { a->offset = 0; } // O(1) "free" of every allocation at once
void arena_destroy(Arena *a) { free(a->base); a->base = NULL; }
#include <stdio.h>
#include "arena.h"
int main(void) {
Arena a = arena_new(1024 * 1024);
if (!a.base) { perror("arena_new"); return 1; }
for (int batch = 0; batch < 3; batch++) {
for (int i = 0; i < 1000; i++) {
int *n = arena_alloc(&a, sizeof(int));
if (!n) { fprintf(stderr, "arena exhausted\n"); arena_destroy(&a); return 1; }
*n = i;
}
printf("batch %d done, resetting\n", batch);
arena_reset(&a); // one call frees all 1000 ints from this batch — no per-object free
}
arena_destroy(&a);
return 0;
}
There is no arena_free(ptr) for individual allocations — that's the
entire point: you trade fine-grained control for O(1) bulk deallocation
and zero fragmentation. This is precisely why compilers (each function's
AST nodes freed all at once after codegen), game engines (per-frame
allocations reset every frame), and request-scoped web-server code
(everything freed when the HTTP response is sent) all use this pattern
instead of individually free()-ing thousands of small objects.