Files
arena.h/arena.h
2026-10-29 22:47:30 +01:00

625 lines
19 KiB
C

// arena.h - v1.2.5 - MIT License
// single header library for region-based memory management.
//
// [License and changelog]
//
// See end of file.
//
// [About this library]
//
// This is a header only, C11 compliant, library for region-based memory
// management.
//
// In this library, arenas are implemented as linked lists of regions. Each
// region will contain all the allocated user buffers in a single block of
// memory. Every time an arena needs a new region to satisfy a new allocation,
// it will append a new one to its internal linked list with an increased
// capacity.
//
// This library also exposes some other utilities, such as snapshot management
// of an arena and common functions used in situations where dynamic memory
// can get in the way of performance and simplicity.
//
// [Quick start example]
//
// #define ARENA_IMPLEMENTATION
// #include "arena.h"
//
// int main(void)
// {
// Arena a = {0}; // or arena_create(...) to specify a custom region
// // capacity just for this arena
//
// int *x = arena_alloc(&a, sizeof(*x) * 32);
// float *y = arena_alloc(&a, sizeof(*y) * 512);
//
// arena_free(&a); // The arena can still be reused
// return 0;
// }
//
// [Single header library usage]
//
// Including this header file as such does what you normally expect.
//
// If you also need to include all of the function implementations you
// need to #define ARENA_IMPLEMENTATION before including the header file
// like this:
//
// #define ARENA_IMPLEMENTATION
// #include "arena.h"
//
// [Error handling]
//
// By default this library handles errors with assertions, thus terminating
// the program with an error message. This happens whenever a memory
// allocation or a critical condition fails.
//
// This behaviour has been chosen as the default one for development
// convenience, since the type of errors this library handles are rare in
// common scenarios.
//
// To disable this behaviour, see ARENA_NO_ASSERT in [Compile-time options]
// below.
//
// [Compile-time options]
//
// Note: every compile-time option listed here should be configured before
// including the header file.
//
// #define ARENA_ALIGNMENT alignment (alignof(max_align_t))
//
// This macro defines the memory alignment used for allocations.
// Must be a power of two. Values greater than alignof(max_align_t)
// are supported.
//
// #define ARENA_REGION_CAPACITY new_region_capacity_in_bytes (8*1024)
//
// This macro defines the default capacity for arena regions.
//
// #define ARENA_REGION_GROWTH_FACTOR new_region_growth_factor (2)
//
// This macro defines the growth factor of the capacity for every
// subsequent region in all arenas.
//
// #define ARENA_NO_ASSERT
//
// This macro disables all assertions that happen when critical
// operations fail. This is useful for checking errors manually with
// return values instead of letting the library assert and terminate
// the program.
//
// #define ARENA_ASSERT my_assert
//
// This macro defines an alternative function for assertions. This has
// no effect when ARENA_NO_ASSERT is defined.
//
// #define ARENA_MALLOC my_malloc
// #define ARENA_FREE my_free
//
// These macros define alternative functions for dynamic allocation
// and deallocation. They are only used for managing Arena__Region
// structures. If redefined ensure that your functions return memory
// aligned at least like malloc() does. ARENA_ALIGNMENT is handled by
// the library itself.
//
// [Function documentation]
//
// Note: every function that starts with the prefix "arena__" is considered a
// private function of this library.
//
// Arena arena_create(size_t region_capacity)
//
// This function is optional (see [Quick start example] section): it
// initializes an arena with a specified region capacity. It will override the
// default ARENA_REGION_CAPACITY just for this arena.
//
// void arena_free(Arena *arena)
//
// This function frees all of the regions allocated in the specified arena
// which invalidates all the pointers associated with it. The arena can still
// be reused. If the arena was created with arena_create() the configured
// region_capacity will be retained.
//
// void arena_reset(Arena *arena)
//
// This function resets an arena by keeping all of the regions allocated but
// invalidates all of the pointers associated with the specified arena.
//
// Warning: this function may cause fragmentation with future allocations.
// Consider setting an appropriate region capacity.
//
// void arena_trim(Arena *arena)
//
// This function frees all of the trailing regions where no buffers are
// allocated. This is useful after an arena_rewind() was performed to save
// memory.
//
// Note: this function always keeps the first region allocated to preserve an
// ideal behaviour.
//
// void *arena_alloc(Arena *arena, size_t size)
//
// This function allocates a buffer of <size> bytes into the specified arena
// and returns its pointer.
//
// void *arena_calloc(Arena *arena, size_t count, size_t size)
//
// This function allocates a buffer of <count> * <size> bytes into the
// specified arena and returns its pointer. The resulting buffer will be
// zero-initialized just like the standard calloc() function.
//
// void *arena_copy(Arena *arena, const void *src, size_t size)
//
// This function allocates a buffer of <size> bytes into the arena and copies
// the contents of <src>. Returns the pointer of the new copy.
//
// char *arena_strdup(Arena *arena, const char *s)
//
// This function acts like the standard strdup() function but it instead
// allocates on the specified arena. It duplicates a NULL terminated string in
// the arena.
//
// char *arena_sprintf(Arena *arena, const char *fmt, ...)
//
// This function uses standard printf style formatting to construct the
// resulting string. It internally uses vsnprintf() to store the resulting
// formatted string in the specified arena.
//
// ArenaMarker arena_snapshot(const Arena *arena)
//
// This function creates a snapshot of the current state of the arena and
// returns it. This is useful when you only need to allocate memory on the arena
// temporarily. To restore the returned state, see arena_rewind().
//
// void arena_rewind(Arena *arena, ArenaMarker marker)
//
// This function restores the arena to the specified state, invalidating all
// previously allocated buffers since the creation of the snapshot.
//
// Note: this function does not free any newly allocated regions internally.
// See arena_trim()
//
// Warning: a marker is invalidated when its region is freed by arena_trim() or
// arena_free(). Rewinding to a marker whose region is no longer part of the
// arena is treated as an error and leaves the arena untouched. This takes
// linear time in the number of regions.
//
// [Notes]
//
// This library does not account for thread safety.
#ifndef ARENA_H_
#define ARENA_H_
#include <stdalign.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#ifndef ARENA_ALIGNMENT
# define ARENA_ALIGNMENT alignof(max_align_t)
#endif // ARENA_ALIGNMENT
#ifndef ARENA_REGION_CAPACITY
# define ARENA_REGION_CAPACITY (8*1024)
#endif // ARENA_REGION_CAPACITY
#ifndef ARENA_REGION_GROWTH_FACTOR
# define ARENA_REGION_GROWTH_FACTOR 2
#endif // ARENA_REGION_GROWTH_FACTOR
#ifndef ARENA_NO_ASSERT
# ifndef ARENA_ASSERT
# include <assert.h>
# define ARENA_ASSERT assert
# endif // ARENA_ASSERT
#else
# undef ARENA_ASSERT
# define ARENA_ASSERT(...) ((void)0)
#endif // ARENA_NO_ASSERT
#ifndef ARENA_MALLOC
# include <stdlib.h>
# define ARENA_MALLOC malloc
#endif // ARENA_MALLOC
#ifndef ARENA_FREE
# include <stdlib.h>
# define ARENA_FREE free
#endif // ARENA_FREE
typedef struct Arena_Region {
struct Arena_Region *next;
uint8_t *data; // Start of user buffers, aligned to ARENA_ALIGNMENT
size_t count;
size_t capacity;
uint8_t storage[];
} Arena__Region;
typedef struct {
Arena__Region *head;
Arena__Region *tail;
size_t region_capacity;
} Arena;
typedef struct {
Arena__Region *region;
size_t count;
} ArenaMarker;
#ifdef __cplusplus
extern "C" { // Prevent name mangling
#endif // __cplusplus
// Arena management
Arena arena_create(size_t region_capacity);
void arena_free(Arena *arena);
void arena_reset(Arena *arena);
void arena_trim(Arena *arena);
// Allocation and other utilities
void *arena_alloc(Arena *arena, size_t size);
void *arena_calloc(Arena *arena, size_t count, size_t size);
void *arena_copy(Arena *arena, const void *src, size_t size);
char *arena_strdup(Arena *arena, const char *s);
char *arena_sprintf(Arena *arena, const char *fmt, ...);
// Snapshot management
ArenaMarker arena_snapshot(const Arena *arena);
void arena_rewind(Arena *arena, ArenaMarker marker);
#ifdef __cplusplus
}
#endif // __cplusplus
#endif // ARENA_H_
#ifdef ARENA_IMPLEMENTATION
// Private functions
static size_t arena__align(size_t n);
static Arena__Region *arena__region_create(size_t capacity);
Arena arena_create(size_t region_capacity)
{
Arena arena;
memset(&arena, 0, sizeof(arena));
arena.region_capacity = region_capacity;
return arena;
}
void arena_free(Arena *arena)
{
if (arena == NULL) {
return;
}
Arena__Region *current = arena->head;
while (current != NULL) {
Arena__Region *next = current->next;
ARENA_FREE(current);
current = next;
}
arena->head = NULL;
arena->tail = NULL;
// Keep the arena region_capacity saved
}
void arena_reset(Arena *arena)
{
if (arena == NULL) {
return;
}
for (Arena__Region *r = arena->head; r != NULL; r = r->next) {
r->count = 0;
}
arena->tail = arena->head;
}
void arena_trim(Arena *arena)
{
if (arena == NULL || arena->tail == NULL) {
return;
}
Arena__Region *to_free = arena->tail->next;
arena->tail->next = NULL;
while (to_free != NULL) {
Arena__Region *next = to_free->next;
ARENA_FREE(to_free);
to_free = next;
}
}
void *arena_alloc(Arena *arena, size_t size)
{
if (arena == NULL || size == 0) {
ARENA_ASSERT(false && "Invalid parameters");
return NULL;
}
size_t aligned_size = arena__align(size);
if (aligned_size < size) {
ARENA_ASSERT(false && "Allocation size overflow after alignment");
return NULL;
}
// Empty arena: allocate first region
if (arena->head == NULL) {
size_t default_capacity = (arena->region_capacity == 0
? ARENA_REGION_CAPACITY
: arena->region_capacity);
size_t alloc_capacity = (aligned_size > default_capacity
? aligned_size
: default_capacity);
arena->head = arena->tail = arena__region_create(alloc_capacity);
if (arena->head == NULL) { return NULL; }
arena->head->count = aligned_size;
return arena->head->data;
}
// Non-empty arena: search existing regions for sufficient capacity
Arena__Region *region = arena->tail;
Arena__Region *last_region = region;
while (region != NULL && aligned_size > region->capacity - region->count) {
last_region = region;
region = region->next;
}
if (region != NULL) {
void *ptr = region->data + region->count;
arena->tail = region;
region->count += aligned_size;
return ptr;
}
// No fitting region found: append a new region
size_t next_capacity = last_region->capacity;
if (next_capacity <= SIZE_MAX / ARENA_REGION_GROWTH_FACTOR) {
next_capacity *= ARENA_REGION_GROWTH_FACTOR;
} else {
next_capacity = SIZE_MAX - sizeof(Arena__Region);
}
size_t alloc_capacity = (aligned_size > next_capacity
? aligned_size
: next_capacity);
last_region->next = arena__region_create(alloc_capacity);
if (last_region->next == NULL) { return NULL; }
arena->tail = last_region->next;
arena->tail->count = aligned_size;
return arena->tail->data;
}
void *arena_calloc(Arena *arena, size_t count, size_t size)
{
if (arena == NULL) {
ARENA_ASSERT(false && "Invalid parameters");
return NULL;
}
// Prevent size_t overflow during arena_alloc size calculation
if (size > 0 && count > SIZE_MAX / size) {
ARENA_ASSERT(false && "Allocation size overflow");
return NULL;
}
size_t alloc_size = count * size;
void *ptr = arena_alloc(arena, alloc_size);
if (ptr == NULL) { return NULL; }
memset(ptr, 0, alloc_size);
return ptr;
}
void *arena_copy(Arena *arena, const void *src, size_t size)
{
if (arena == NULL || src == NULL) {
ARENA_ASSERT(false && "Invalid parameters");
return NULL;
}
void *copy = arena_alloc(arena, size);
if (copy == NULL) { return NULL; }
memcpy(copy, src, size);
return copy;
}
char *arena_strdup(Arena *arena, const char *s)
{
if (arena == NULL || s == NULL) {
ARENA_ASSERT(false && "Invalid parameters");
return NULL;
}
char *copy = (char *)arena_copy(arena, s, strlen(s) + 1);
if (copy == NULL) { return NULL; }
return copy;
}
char *arena_sprintf(Arena *arena, const char *fmt, ...)
{
if (arena == NULL || fmt == NULL) {
ARENA_ASSERT(false && "Invalid parameters");
return NULL;
}
va_list args;
va_start(args, fmt);
va_list args_copy;
va_copy(args_copy, args);
int length = vsnprintf(NULL, 0, fmt, args_copy);
va_end(args_copy);
if (length < 0) {
ARENA_ASSERT(false && "Format string length calculation failed");
va_end(args);
return NULL;
}
size_t size = (size_t)length + 1;
char *str = (char *)arena_alloc(arena, size);
if (str == NULL) {
va_end(args);
return NULL;
}
int written = vsnprintf(str, size, fmt, args);
ARENA_ASSERT(written == length && "Mismatched vsnprintf on second pass");
if (written < 0) {
ARENA_ASSERT(false && "Failed vsnprintf on second pass");
va_end(args);
return NULL;
}
va_end(args);
return str;
}
ArenaMarker arena_snapshot(const Arena *arena)
{
ArenaMarker marker;
memset(&marker, 0, sizeof(marker));
if (arena == NULL) {
ARENA_ASSERT(false && "Invalid parameters");
return marker;
}
marker.region = arena->tail;
marker.count = (arena->tail != NULL ? arena->tail->count : 0);
return marker;
}
void arena_rewind(Arena *arena, ArenaMarker marker)
{
if (arena == NULL) {
ARENA_ASSERT(false && "Invalid parameters");
return;
}
// If arena was empty reset it
if (marker.region == NULL) {
arena_reset(arena);
return;
}
// Reject markers whose region is not part of this arena anymore, e.g.
// freed by arena_trim() or arena_free(), or taken from another arena
Arena__Region *region = arena->head;
while (region != NULL && region != marker.region) {
region = region->next;
}
if (region == NULL || marker.count > region->capacity) {
ARENA_ASSERT(false && "Invalid marker");
return;
}
arena->tail = region;
arena->tail->count = marker.count;
// Reset any new regions that were appended
for (Arena__Region *r = arena->tail->next; r != NULL; r = r->next) {
r->count = 0;
}
}
static size_t arena__align(size_t n)
{
return (n + (ARENA_ALIGNMENT - 1)) & ~(ARENA_ALIGNMENT - 1);
}
static Arena__Region *arena__region_create(size_t capacity)
{
// Extra space to align data ourselves, since ARENA_MALLOC only guarantees
// alignof(max_align_t), which may be less than ARENA_ALIGNMENT
size_t padding = ARENA_ALIGNMENT - 1;
if (capacity > SIZE_MAX - sizeof(Arena__Region) - padding) {
ARENA_ASSERT(false && "Region allocation size overflow");
return NULL;
}
Arena__Region *region = (Arena__Region *)ARENA_MALLOC(
sizeof(*region) + padding + capacity);
if (region == NULL) {
ARENA_ASSERT(false && "Memory allocation failed");
return NULL;
}
uintptr_t misalignment = (uintptr_t)region->storage
& ((uintptr_t)ARENA_ALIGNMENT - 1);
region->data = region->storage + (misalignment == 0
? 0
: ARENA_ALIGNMENT - misalignment);
region->next = NULL;
region->count = 0;
region->capacity = capacity;
return region;
}
#endif // ARENA_IMPLEMENTATION
/*
* Revision history:
*
* 1.2.5 (2026-10-28) Support ARENA_ALIGNMENT > alignof(max_align_t);
* reject invalid markers in arena_rewind()
* 1.2.4 (2026-09-20) Major rewrite
* 1.2.3 (2026-07-07) New function: arena_calloc()
* 1.2.2 (2026-07-07) Fix various memory problems;
* make ARENA_REGION_GROWTH_FACTOR an integer
* 1.2.1 (2026-04-08) Region growth factor handling with new
* ARENA_REGION_GROWTH_FACTOR macro
* 1.2.0 (2026-04-08) New functions: arena_sprintf(), arena_snapshot(),
* arena_rewind() and arena_trim()
* 1.1.3 (2026-01-14) Align Arena_Region data flexible array member
* 1.1.2 (2026-01-10) Minor changes; check result of alloc in arena_copy()
* 1.1.1 (2025-11-18) Implement memory alignment; improve docs
* 1.1.0 (2025-11-18) New helper functions: arena_copy(), arena_strdup()
* 1.0.3 (2025-09-10) Renamed parameter bytes to size in arena_alloc()
* 1.0.2 (2025-09-06) Bug fixes; new ARENA_NO_ASSERT macro
* 1.0.1 (2025-08-02) Prevent name mangling of functions; don't reset
* region_capacity in arena_free()
* 1.0.0 (2025-07-24) Initial release
*/
/*
* MIT License
*
* Copyright (c) 2026 Cristian Gora
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/