Frontend Labs
⋯

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.


Generated from real-life-c-guide.md · 8 Oct 2026 16:36