An arena allocator is a memory allocation technique used in computer programming where memory is allocated from a pre-allocated block (arena) rather than individually requesting memory from the operating system.
Once allocated, memory within the arena typically remains allocated until the entire arena is deallocated, simplifying memory management and reducing the risk of memory leaks.
Working:
The whole implementation is about 55 lines of code (besides the test run).
The API is straightforward: begin with arena_new() and end with arena_destroy(). Memory allocations are
made using arena_alloc(), without the need for manual deallocation or tracking. The arena dynamically resizes
as needed.
An example follows:
#include <stdio.h>
#include <stdlib.h>
#include "arena.h"
int main(void) {
// Create a new arena
Arena *arena = arena_new();
// Allocate memory within the arena
int *data = arena_alloc(arena, sizeof *data);
// Deallocate memory and destroy the arena
arena_destroy(arena);
return EXIT_SUCCESS;
}
Code:
arena.h:
#ifndef ARENA_H
#define ARENA_H 1
#include <stddef.h>
#if defined(__GNUC__) || defined(__clang__) || defined(__INTEL_LLVM_COMPILER)
#define ATTRIB_NONNULL __attribute__((nonnull))
#else
#define ATTRIB_NONNULL /**/
#endif
typedef struct arena Arena;
Arena *arena_new(void);
void *arena_alloc(Arena *arena, size_t alloc_size) ATTRIB_NONNULL;
void arena_destroy(Arena *arena);
#endif /* ARENA_H */
arena.c:
#include "arena.h"
#include <stdint.h>
#include <stdlib.h>
/* In C2X/C23 or later, nullptr is a keyword. */
/* Patch up C18 (__STDC_VERSION__ == 201710L) and earlier versions. */
#if !defined(__STDC_VERSION__) || __STDC_VERSION__ <= 201710L
#define nullptr ((void *)0)
#endif
#define ARENA_INITIAL_CAPACITY 10 * (size_t)1024
#define GROW_CAPACITY(capacity, initial) \
((capacity) < initial ? initial : (capacity) * 2)
struct arena {
size_t count;
size_t capacity;
uint8_t pool[];
};
Arena *arena_new(void)
{
Arena *a = calloc(1, sizeof *a + ARENA_INITIAL_CAPACITY * sizeof a->pool[0]);
a->capacity = ARENA_INITIAL_CAPACITY;
return a;
}
void *arena_alloc(Arena *arena, size_t alloc_size)
{
if ((arena->count + alloc_size) >= arena->capacity) {
arena->capacity =
GROW_CAPACITY(arena->capacity, ARENA_INITIAL_CAPACITY);
Arena *a = realloc(arena,
sizeof *arena + arena->capacity * sizeof arena->pool[0]);
if (a == nullptr) {
return nullptr;
}
arena = a;
}
void *result = arena->pool + arena->count;
arena->count += alloc_size;
return result;
}
void arena_destroy(Arena *arena)
{
free(arena);
}
#ifdef TEST_MAIN
#include <stdio.h>
int main(void)
{
Arena *const arena = arena_new();
if (arena == nullptr) {
fprintf(stderr, "error: arena_new(): failed to allocate memory.\n");
return EXIT_FAILURE;
}
int *a = arena_alloc(arena, sizeof *a);
int *b = arena_alloc(arena, sizeof *b);
int *c = arena_alloc(arena, sizeof *c);
if (a == nullptr || b == nullptr || c == nullptr) {
arena_destroy(arena);
fprintf(stderr, "error: arena_alloc(): failed to allocate memory.\n");
return EXIT_FAILURE;
}
*a = 1;
*b = 2;
*c = 3;
printf("&a: %p, a: %d\n"
"&b: %p, b: %d\n"
"&c: %p, c: %d\n",
(void *)a,
*a,
(void *)b,
*b,
(void *)c,
*c);
arena_destroy(arena);
return EXIT_SUCCESS;
}
#endif /* TEST_MAIN */
Makefile:
CC := gcc-13
CFLAGS += -std=c2x
CFLAGS += -fPIC
CFLAGS += -Wall
CFLAGS += -Wextra
CFLAGS += -Wpedantic
CFLAGS += -Wwrite-strings
CFLAGS += -Wno-parentheses
CFLAGS += -Wpedantic
CFLAGS += -Warray-bounds
CFLAGS += -Wno-unused-function
CFLAGS += -Wstrict-prototypes
all: arena
test: CFLAGS += -DTEST_MAIN
test: arena
clean:
$(RM) arena
.PHONY: all test clean
.DELETE_ON_ERROR:
Building:
To build a sample program, simply do:
make test
./arena
For ease, here's the github link: An Arena Allocator in C
Review Request:
Possible optimizations, potential undefined/implementation-defined behavior, integer overflow/underflow, style et cetera.
Is the initial pool size okay? Should it be higher or lower?
makealone.make testsounds like I have tests. \$\endgroup\$