625 lines
19 KiB
C
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.
|
|
*/
|