v1.2.4 - Major rewrite

This commit is contained in:
2026-09-20 18:52:14 +02:00
parent 7b6af7028f
commit 4cfd4fd5f9
5 changed files with 495 additions and 322 deletions

16
.clangd Normal file
View File

@@ -0,0 +1,16 @@
CompileFlags:
Add:
- "-Wall"
- "-Wextra"
- "-pedantic"
- "-std=c11"
---
If:
PathMatch: arena\.h
CompileFlags:
Add:
- "-xc-header"
- "-DARENA_IMPLEMENTATION"

View File

@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2025 seajee Copyright (c) 2026 Cristian Gora
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,6 +1,6 @@
test: test.c arena.h test: test.c arena.h
cc -Wall -Wextra -DDEBUG -o test test.c clang -Wall -Wextra -pedantic -std=c11 -o test test.c
clean: clean:
rm -rf test rm -rf test

550
arena.h
View File

@@ -1,10 +1,42 @@
// arena.h - v1.2.3 - MIT License // arena.h - v1.2.4 - MIT License
// single header library for region-based memory management. // single header library for region-based memory management.
// //
// [License and changelog] // [License and changelog]
// //
// See end of file. // 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] // [Single header library usage]
// //
// Including this header file as such does what you normally expect. // Including this header file as such does what you normally expect.
@@ -16,10 +48,28 @@
// #define ARENA_IMPLEMENTATION // #define ARENA_IMPLEMENTATION
// #include "arena.h" // #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] // [Compile-time options]
// //
// Note: every compile-time option listed here should be configured before // Note: every compile-time option listed here should be configured before
// the #include "arena.h" // 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.
// //
// #define ARENA_REGION_CAPACITY new_region_capacity_in_bytes (8*1024) // #define ARENA_REGION_CAPACITY new_region_capacity_in_bytes (8*1024)
// //
@@ -30,17 +80,12 @@
// This macro defines the growth factor of the capacity for every // This macro defines the growth factor of the capacity for every
// subsequent region in all arenas. // subsequent region in all arenas.
// //
// #define ARENA_ALIGNMENT alignment (alignof(max_align_t))
//
// This macro defines the memory alignment used for allocations.
// Must be a power of two.
//
// #define ARENA_NO_ASSERT // #define ARENA_NO_ASSERT
// //
// This macro disables assertions that happen when allocations fail. // This macro disables all assertions that happen when critical
// The disabled assertions will be replaced with a simple condition // operations fail. This is useful for checking errors manually with
// that returns NULL when it fails. This macro affects every function // return values instead of letting the library assert and terminate
// that returns a pointer. // the program.
// //
// #define ARENA_ASSERT my_assert // #define ARENA_ASSERT my_assert
// //
@@ -51,116 +96,106 @@
// #define ARENA_FREE my_free // #define ARENA_FREE my_free
// //
// These macros define alternative functions for dynamic allocation // These macros define alternative functions for dynamic allocation
// and deallocation. They are only used for managing Arena_Region // and deallocation. They are only used for managing Arena__Region
// structures. If redefined ensure that your functions consider memory // structures. If redefined ensure that your functions consider memory
// alignment. // alignment.
// //
// [Function documentation] // [Function documentation]
// //
// Note: every function that starts with "arena__" is considered a private // Note: every function that starts with the prefix "arena__" is considered a
// function of this library. // private function of this library.
//
// In this library, arenas are implemented as linked lists of regions. Each
// region will contain the allocated buffers. The following are all of the
// functions
// //
// Arena arena_create(size_t region_capacity) // Arena arena_create(size_t region_capacity)
// //
// This function is optional (see [Example] section): initializes an arena // This function is optional (see [Quick start example] section): it
// with a specified region capacity. It will override the default // initializes an arena with a specified region capacity. It will override the
// ARENA_REGION_CAPACITY. // default ARENA_REGION_CAPACITY just for this arena.
// //
// void arena_free(Arena *a) // void arena_free(Arena *arena)
// //
// This function frees all of the regions allocated in the specified arena // This function frees all of the regions allocated in the specified arena
// which invalidates all the pointers associated with the arena. The arena can // which invalidates all the pointers associated with it. The arena can still
// still be reused. If the arena was created with arena_create() the // be reused. If the arena was created with arena_create() the configured
// configured region_capacity will be retained. // region_capacity will be retained.
// //
// void arena_reset(Arena *a) // void arena_reset(Arena *arena)
// //
// This function resets an arena by keeping all of the regions allocated but // This function resets an arena by keeping all of the regions allocated but
// invalidates all of the pointers associated with the specified arena. // invalidates all of the pointers associated with the specified arena.
// Warning: this functions may cause fragmentation, consider setting an
// appropriate region capacity.
// //
// void arena_trim(Arena *a) // 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.
// //
// This functions frees all of the regions where no buffers are allocated.
// This is useful after an arena_rewind() was performed to preserve memory.
// Note: this function always keeps the first region allocated to preserve an // Note: this function always keeps the first region allocated to preserve an
// ideal behaviour. // ideal behaviour.
// //
// void *arena_alloc(Arena *a, size_t size) // void *arena_alloc(Arena *arena, size_t size)
// //
// This function allocates a buffer of <size> bytes into the specified arena // This function allocates a buffer of <size> bytes into the specified arena
// and returns it's pointer. // and returns its pointer.
// //
// void *arena_calloc(Arena *a, size_t count, size_t size) // void *arena_calloc(Arena *arena, size_t count, size_t size)
// //
// This function allocates a buffer of <count> * <size> bytes into the // This function allocates a buffer of <count> * <size> bytes into the
// specified arena and returns it's pointer. The resulting buffer will be // specified arena and returns its pointer. The resulting buffer will be
// zero-initialized just like the standard calloc() function. // zero-initialized just like the standard calloc() function.
// //
// void *arena_copy(Arena *a, const void *src, size_t size); // void *arena_copy(Arena *arena, const void *src, size_t size)
// //
// This function allocates a buffer of <size> bytes into the arena and copies // This function allocates a buffer of <size> bytes into the arena and copies
// the contents of <src>. Returns the pointer of the new copy. // the contents of <src>. Returns the pointer of the new copy.
// //
// char *arena_strdup(Arena *a, const char *s); // char *arena_strdup(Arena *arena, const char *s)
// //
// This functions acts like the strdup() function but it instead allocates on // This function acts like the standard strdup() function but it instead
// the specified arena. It duplicates a string in the arena. // allocates on the specified arena. It duplicates a NULL terminated string in
// the arena.
// //
// char *arena_sprintf(Arena *a, const char *fmt, ...) // char *arena_sprintf(Arena *arena, const char *fmt, ...)
// //
// This function internally uses vsnprintf() to store the resulting formatted // This function uses standard printf style formatting to construct the
// string in the specified arena. Returns NULL on error. // resulting string. It internally uses vsnprintf() to store the resulting
// formatted string in the specified arena.
// //
// Arena_Marker arena_snapshot(const Arena *a) // ArenaMarker arena_snapshot(const Arena *arena)
// //
// This function creates a snapshot of the current state of the arena and // 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 // returns it. This is useful when you only need to allocate memory on the arena
// temporarily. To restore the returned state, see arena_rewind(). // temporarily. To restore the returned state, see arena_rewind().
// //
// void arena_rewind(Arena *a, Arena_Marker marker) // void arena_rewind(Arena *arena, ArenaMarker marker)
// //
// This function restores the arena to the specified state, invalidating all // This function restores the arena to the specified state, invalidating all
// previously allocated buffers since the creation of the snapshot. // previously allocated buffers since the creation of the snapshot.
// //
// Note: this function does not free any newly allocated regions internally.
// See arena_trim()
//
// [Notes] // [Notes]
// //
// This library is not Thread-safe. // This library does not account for thread safety.
//
// [Example]
//
#if 0
#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;
}
#endif // [Example]
#ifndef ARENA_H_ #ifndef ARENA_H_
#define ARENA_H_ #define ARENA_H_
#include <stdalign.h> #include <stdalign.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#ifndef ARENA_ALIGNMENT
# define ARENA_ALIGNMENT alignof(max_align_t)
#endif // ARENA_ALIGNMENT
#ifndef ARENA_REGION_CAPACITY #ifndef ARENA_REGION_CAPACITY
# define ARENA_REGION_CAPACITY (8*1024) # define ARENA_REGION_CAPACITY (8*1024)
#endif // ARENA_REGION_CAPACITY #endif // ARENA_REGION_CAPACITY
@@ -169,10 +204,6 @@ int main(void)
# define ARENA_REGION_GROWTH_FACTOR 2 # define ARENA_REGION_GROWTH_FACTOR 2
#endif // ARENA_REGION_GROWTH_FACTOR #endif // ARENA_REGION_GROWTH_FACTOR
#ifndef ARENA_ALIGNMENT
# define ARENA_ALIGNMENT alignof(max_align_t)
#endif // ARENA_ALIGNMENT
#ifndef ARENA_NO_ASSERT #ifndef ARENA_NO_ASSERT
# ifndef ARENA_ASSERT # ifndef ARENA_ASSERT
# include <assert.h> # include <assert.h>
@@ -192,49 +223,44 @@ int main(void)
# define ARENA_FREE free # define ARENA_FREE free
#endif // ARENA_FREE #endif // ARENA_FREE
#ifdef __cplusplus typedef struct Arena_Region {
extern "C" { // Prevent name mangling of functions struct Arena_Region *next;
#endif // __cplusplus
typedef struct Arena_Region Arena_Region;
struct Arena_Region {
Arena_Region *next;
size_t count; size_t count;
size_t capacity; size_t capacity;
alignas(ARENA_ALIGNMENT) uint8_t data[]; alignas(ARENA_ALIGNMENT) uint8_t data[];
}; } Arena__Region;
typedef struct { typedef struct {
Arena_Region *region; Arena__Region *head;
size_t count; Arena__Region *tail;
} Arena_Marker;
typedef struct Arena {
Arena_Region *head;
Arena_Region *tail;
size_t region_capacity; size_t region_capacity;
} Arena; } Arena;
typedef struct {
Arena__Region *region;
size_t count;
} ArenaMarker;
#ifdef __cplusplus
extern "C" { // Prevent name mangling
#endif // __cplusplus
// Arena management // Arena management
Arena arena_create(size_t region_capacity); Arena arena_create(size_t region_capacity);
void arena_free(Arena *a); void arena_free(Arena *arena);
void arena_reset(Arena *a); void arena_reset(Arena *arena);
void arena_trim(Arena *a); void arena_trim(Arena *arena);
// Allocation and other utilities // Allocation and other utilities
void *arena_alloc(Arena *a, size_t size); void *arena_alloc(Arena *arena, size_t size);
void *arena_calloc(Arena *a, size_t count, size_t size); void *arena_calloc(Arena *arena, size_t count, size_t size);
void *arena_copy(Arena *a, const void *src, size_t size); void *arena_copy(Arena *arena, const void *src, size_t size);
char *arena_strdup(Arena *a, const char *s); char *arena_strdup(Arena *arena, const char *s);
char *arena_sprintf(Arena *a, const char *fmt, ...); char *arena_sprintf(Arena *arena, const char *fmt, ...);
// Snapshot management // Snapshot management
Arena_Marker arena_snapshot(const Arena *a); ArenaMarker arena_snapshot(const Arena *arena);
void arena_rewind(Arena *a, Arena_Marker marker); void arena_rewind(Arena *arena, ArenaMarker marker);
// Private functions
size_t arena__align(size_t n);
#ifdef __cplusplus #ifdef __cplusplus
} }
@@ -244,197 +270,203 @@ size_t arena__align(size_t n);
#ifdef ARENA_IMPLEMENTATION #ifdef ARENA_IMPLEMENTATION
#ifdef __cplusplus // Private functions
extern "C" { // Prevent name mangling of functions static size_t arena__align(size_t n);
#endif // __cplusplus
Arena arena_create(size_t region_capacity) Arena arena_create(size_t region_capacity)
{ {
Arena a; Arena arena;
memset(&a, 0, sizeof(a)); memset(&arena, 0, sizeof(arena));
a.region_capacity = region_capacity;
return a; arena.region_capacity = region_capacity;
return arena;
} }
void arena_free(Arena *a) void arena_free(Arena *arena)
{ {
if (a == NULL) { if (arena == NULL) {
return; return;
} }
Arena_Region *cur = a->head; Arena__Region *current = arena->head;
while (cur != NULL) {
Arena_Region *next = cur->next; while (current != NULL) {
ARENA_FREE(cur); Arena__Region *next = current->next;
cur = next; ARENA_FREE(current);
current = next;
} }
a->head = NULL; arena->head = NULL;
a->tail = NULL; arena->tail = NULL;
// Keep the arena region_capacity saved
} }
void arena_reset(Arena *a) void arena_reset(Arena *arena)
{ {
if (a == NULL) { if (arena == NULL) {
return; return;
} }
for (Arena_Region *cur = a->head; cur != NULL; cur = cur->next) { for (Arena__Region *r = arena->head; r != NULL; r = r->next) {
cur->count = 0; r->count = 0;
} }
a->tail = a->head; arena->tail = arena->head;
} }
void arena_trim(Arena *a) void arena_trim(Arena *arena)
{ {
if (a == NULL) { if (arena == NULL || arena->tail == NULL) {
return; return;
} }
Arena_Region *to_free = a->tail->next; Arena__Region *to_free = arena->tail->next;
a->tail->next = NULL; arena->tail->next = NULL;
while (to_free != NULL) { while (to_free != NULL) {
Arena_Region *next = to_free->next; Arena__Region *next = to_free->next;
ARENA_FREE(to_free); ARENA_FREE(to_free);
to_free = next; to_free = next;
} }
} }
void *arena_alloc(Arena *a, size_t size) void *arena_alloc(Arena *arena, size_t size)
{ {
if (a == NULL || size == 0) { if (arena == NULL || size == 0) {
ARENA_ASSERT(false && "Invalid parameters"); ARENA_ASSERT(false && "Invalid parameters");
return NULL; return NULL;
} }
// Check for overflow size_t aligned_size = arena__align(size);
if (size > SIZE_MAX - sizeof(Arena_Region)) { if (aligned_size < size) {
ARENA_ASSERT(false && "Requested size too large"); ARENA_ASSERT(false && "Allocation size overflow after alignment");
return NULL;
}
// Ensure memory alignment of requested size
size = arena__align(size);
// Get the next region capacity multiplied by the growth factor
size_t region_capacity = (a->region_capacity == 0
? ARENA_REGION_CAPACITY : a->region_capacity);
if (a->tail != NULL) {
size_t max_cap = (SIZE_MAX / 2) - sizeof(Arena_Region);
if (a->tail->capacity >= max_cap / (ARENA_REGION_GROWTH_FACTOR)) {
region_capacity = max_cap;
} else {
region_capacity = a->tail->capacity * (ARENA_REGION_GROWTH_FACTOR);
// Edge case for small growth factors
if (region_capacity <= a->tail->capacity) {
region_capacity = a->tail->capacity + (4 * 1024);
}
}
}
// Calculate and check region allocation size
size_t alloc_size = (size > region_capacity ? size : region_capacity);
if (alloc_size > SIZE_MAX - sizeof(Arena_Region)) {
ARENA_ASSERT(false && "Region size overflow");
return NULL; return NULL;
} }
// Empty arena: allocate first region // Empty arena: allocate first region
if (a->head == NULL) { if (arena->head == NULL) {
a->head = (Arena_Region*)ARENA_MALLOC(sizeof(*a->head) + alloc_size); size_t default_capacity = (arena->region_capacity == 0
if (a->head == NULL) { ? ARENA_REGION_CAPACITY
ARENA_ASSERT(false && "Allocation failed"); : arena->region_capacity);
size_t alloc_capacity = (aligned_size > default_capacity
? aligned_size
: default_capacity);
if (alloc_capacity > SIZE_MAX - sizeof(Arena__Region)) {
ARENA_ASSERT(false && "Region allocation size overflow");
return NULL; return NULL;
} }
a->head->next = NULL; arena->head = arena->tail = (Arena__Region *)ARENA_MALLOC(
a->head->count = size; sizeof(*arena->head) + alloc_capacity);
a->head->capacity = alloc_size; if (arena->head == NULL) {
a->tail = a->head; ARENA_ASSERT(false && "Memory allocation failed");
return a->head->data;
}
// Find first suitable region
Arena_Region *r = a->tail;
Arena_Region *last = r;
while (r != NULL && size > r->capacity - r->count) {
last = r;
r = r->next;
}
// If found allocate on it
if (r != NULL) {
if (r->count > SIZE_MAX - size) {
ARENA_ASSERT(false && "Region count overflow");
return NULL;
}
a->tail = r;
r->count += size;
return r->data + r->count - size;
}
// If not found append a new region
last->next = (Arena_Region*)ARENA_MALLOC(sizeof(*a->tail) + alloc_size);
if (last->next == NULL) {
ARENA_ASSERT(false && "Allocation failed");
return NULL; return NULL;
} }
a->tail = last->next; arena->head->next = NULL;
a->tail->next = NULL; arena->head->count = aligned_size;
a->tail->count = size; arena->head->capacity = alloc_capacity;
a->tail->capacity = alloc_size; return arena->head->data;
return a->tail->data;
} }
void *arena_calloc(Arena *a, size_t count, size_t size) // 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);
if (alloc_capacity > SIZE_MAX - sizeof(Arena__Region)) {
ARENA_ASSERT(false && "Region allocation size overflow");
return NULL;
}
last_region->next = (Arena__Region *)ARENA_MALLOC(
sizeof(*last_region->next) + alloc_capacity);
if (last_region->next == NULL) {
ARENA_ASSERT(false && "Memory allocation failed");
return NULL;
}
arena->tail = last_region->next;
arena->tail->next = NULL;
arena->tail->count = aligned_size;
arena->tail->capacity = alloc_capacity;
return arena->tail->data;
}
void *arena_calloc(Arena *arena, size_t count, size_t size)
{ {
if (a == NULL) { if (arena == NULL) {
ARENA_ASSERT(false && "Invalid parameters"); ARENA_ASSERT(false && "Invalid parameters");
return NULL; return NULL;
} }
void *result = arena_alloc(a, count * size); // Prevent size_t overflow during arena_alloc size calculation
if (size > 0 && count > SIZE_MAX / size) {
if (result != NULL) { ARENA_ASSERT(false && "Allocation size overflow");
memset(result, 0, count * size); return NULL;
} }
return result; 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 *a, const void *src, size_t size) void *arena_copy(Arena *arena, const void *src, size_t size)
{ {
if (a == NULL || src == NULL) { if (arena == NULL || src == NULL) {
ARENA_ASSERT(false && "Invalid parameters"); ARENA_ASSERT(false && "Invalid parameters");
return NULL; return NULL;
} }
void *copy = arena_alloc(a, size); void *copy = arena_alloc(arena, size);
if (copy == NULL) { if (copy == NULL) { return NULL; }
return NULL;
}
memcpy(copy, src, size); 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; return copy;
} }
char *arena_strdup(Arena *a, const char *s) char *arena_sprintf(Arena *arena, const char *fmt, ...)
{ {
if (a == NULL || s == NULL) { if (arena == NULL || fmt == NULL) {
ARENA_ASSERT(false && "Invalid parameters");
return NULL;
}
return (char*)arena_copy(a, s, strlen(s) + 1);
}
char *arena_sprintf(Arena *a, const char *fmt, ...)
{
if (a == NULL || fmt == NULL) {
ARENA_ASSERT(false && "Invalid parameters"); ARENA_ASSERT(false && "Invalid parameters");
return NULL; return NULL;
} }
@@ -442,105 +474,83 @@ char *arena_sprintf(Arena *a, const char *fmt, ...)
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
Arena_Region *r = a->tail;
char *result = NULL;
size_t available = 0;
if (r != NULL) {
available = r->capacity - r->count;
if (available > 0) {
result = (char*)r->data + r->count;
}
}
// Check if result fits inside tail's region available space
va_list args_copy; va_list args_copy;
va_copy(args_copy, args); va_copy(args_copy, args);
int n = vsnprintf(result, available, fmt, args_copy); int length = vsnprintf(NULL, 0, fmt, args_copy);
va_end(args_copy); va_end(args_copy);
if (length < 0) {
if (n < 0) { ARENA_ASSERT(false && "Format string length calculation failed");
va_end(args); va_end(args);
return NULL; return NULL;
} }
size_t needed = (size_t)n + 1; size_t size = (size_t)length + 1;
char *str = (char *)arena_alloc(arena, size);
// If it fits commit and return if (str == NULL) {
if (arena__align(needed) <= available) {
r->count += arena__align(needed);
va_end(args);
return result;
}
// Fallback: if it didn't fit, allocate and printf
result = (char*)arena_alloc(a, needed);
if (result == NULL) {
va_end(args); va_end(args);
return NULL; return NULL;
} }
n = vsnprintf(result, needed, fmt, args); 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); va_end(args);
if (n < 0) {
return NULL; return NULL;
} }
return result; va_end(args);
return str;
} }
Arena_Marker arena_snapshot(const Arena *a) ArenaMarker arena_snapshot(const Arena *arena)
{ {
Arena_Marker marker = {0}; ArenaMarker marker;
memset(&marker, 0, sizeof(marker));
if (a == NULL) { if (arena == NULL) {
ARENA_ASSERT(false && "Invalid parameters"); ARENA_ASSERT(false && "Invalid parameters");
return marker; return marker;
} }
marker.region = a->tail; marker.region = arena->tail;
marker.count = (a->tail != NULL ? a->tail->count : 0); marker.count = (arena->tail != NULL ? arena->tail->count : 0);
return marker; return marker;
} }
void arena_rewind(Arena *a, Arena_Marker marker) void arena_rewind(Arena *arena, ArenaMarker marker)
{ {
if (a == NULL) { if (arena == NULL) {
ARENA_ASSERT(false && "Invalid parameters"); ARENA_ASSERT(false && "Invalid parameters");
return; return;
} }
// If arena was empty reset it // If arena was empty reset it
if (marker.region == NULL) { if (marker.region == NULL) {
arena_reset(a); arena_reset(arena);
return; return;
} }
a->tail = marker.region; arena->tail = marker.region;
a->tail->count = marker.count; arena->tail->count = marker.count;
// Clear new regions in case they were appended // Reset any new regions that were appended
for (Arena_Region *cur = a->tail->next; cur != NULL; cur = cur->next) { for (Arena__Region *r = arena->tail->next; r != NULL; r = r->next) {
cur->count = 0; r->count = 0;
} }
} }
size_t arena__align(size_t n) static size_t arena__align(size_t n)
{ {
return (n + (ARENA_ALIGNMENT - 1)) & ~(ARENA_ALIGNMENT - 1); return (n + (ARENA_ALIGNMENT - 1)) & ~(ARENA_ALIGNMENT - 1);
} }
#ifdef __cplusplus
}
#endif // __cplusplus
#endif // ARENA_IMPLEMENTATION #endif // ARENA_IMPLEMENTATION
/* /*
* Revision history: * Revision history:
* *
* 1.2.4 (2026-09-20) Major rewrite
* 1.2.3 (2026-07-07) New function: arena_calloc() * 1.2.3 (2026-07-07) New function: arena_calloc()
* 1.2.2 (2026-07-07) Fix various memory problems; * 1.2.2 (2026-07-07) Fix various memory problems;
* make ARENA_REGION_GROWTH_FACTOR an integer * make ARENA_REGION_GROWTH_FACTOR an integer
@@ -562,7 +572,7 @@ size_t arena__align(size_t n)
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2025 seajee * Copyright (c) 2026 Cristian Gora
* *
* Permission is hereby granted, free of charge, to any person obtaining a * Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"), * copy of this software and associated documentation files (the "Software"),

249
test.c
View File

@@ -1,64 +1,211 @@
#include <assert.h>
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <string.h>
#ifdef DEBUG
#define malloc(s) (printf("%s:%d:%s: malloc(%lu)\n",\
__FILE__, __LINE__, __func__, (s)), malloc((s)));
#define free(p) (printf("%s:%d:%s: free(%p)\n",\
__FILE__, __LINE__, __func__, (p)), free((p)));
#endif // DEBUG
#define ARENA_IMPLEMENTATION #define ARENA_IMPLEMENTATION
#define ARENA_REGION_GROWTH_FACTOR 2 #define ARENA_NO_ASSERT
#include "arena.h" #include "arena.h"
void arena_print(Arena arena) // Helper to count active regions in the linked list
static int count_regions(const Arena *arena)
{ {
int i = 0; int count = 0;
for (Arena_Region *cur = arena.head; cur != NULL; cur = cur->next) { for (Arena__Region *r = arena->head; r != NULL; r = r->next) {
printf("Arena_Region #%d:\n", ++i); count++;
printf(" count = %ld\n", cur->count);
printf(" capacity = %ld\n", cur->capacity);
printf(" next = %p\n", cur->next);
printf(" data = %p\n", cur->data);
} }
return count;
}
// Helper to verify pointer alignment
static bool is_aligned(const void *ptr)
{
return ((uintptr_t)ptr % ARENA_ALIGNMENT) == 0;
}
static void test_edge_case_inputs(void)
{
printf("Running edge case and invalid input tests...\n");
Arena arena = arena_create(1024);
// 0-byte allocations
assert(arena_alloc(&arena, 0) == NULL);
assert(arena_calloc(&arena, 0, 10) == NULL);
assert(arena_calloc(&arena, 10, 0) == NULL);
// NULL pointer handling
assert(arena_alloc(NULL, 100) == NULL);
assert(arena_calloc(NULL, 1, 1) == NULL);
assert(arena_copy(NULL, "data", 4) == NULL);
assert(arena_copy(&arena, NULL, 4) == NULL);
assert(arena_strdup(NULL, "test") == NULL);
assert(arena_strdup(&arena, NULL) == NULL);
assert(arena_sprintf(NULL, "test") == NULL);
assert(arena_sprintf(&arena, NULL) == NULL);
// Snapshot/Rewind/Trim edge cases on NULL or empty
arena_trim(NULL); // Should not crash
arena_reset(NULL); // Should not crash
arena_free(NULL); // Should not crash
ArenaMarker null_marker = arena_snapshot(NULL);
assert(null_marker.region == NULL && null_marker.count == 0);
arena_rewind(NULL, null_marker); // Should not crash
arena_free(&arena);
}
static void test_overflows(void)
{
printf("Running integer overflow tests...\n");
Arena arena = arena_create(1024);
// calloc multiplication overflow (count * size > SIZE_MAX)
size_t massive_count = SIZE_MAX / 2;
size_t massive_size = 4;
assert(arena_calloc(&arena, massive_count, massive_size) == NULL);
// arena_alloc size wrap-around on alignment
// arena__align(SIZE_MAX) will overflow back to a small number
size_t overflow_align_size = SIZE_MAX - (ARENA_ALIGNMENT / 2);
assert(arena_alloc(&arena, overflow_align_size) == NULL);
arena_free(&arena);
}
static void test_geometric_growth_and_stress(void)
{
printf("Running geometric growth and stress tests...\n");
size_t initial_capacity = 64;
Arena arena = arena_create(initial_capacity);
// Exhaust the first region to force geometric growth
assert(arena_alloc(&arena, 32) != NULL); // Head allocated with capacity 64
assert(arena.head->capacity == 64);
assert(arena_alloc(&arena, 32) != NULL); // Head is now full
// Trigger next region (should be 64 * 2 = 128)
assert(arena_alloc(&arena, 32) != NULL);
assert(arena.tail->capacity == 128);
// Trigger next region (should be 128 * 2 = 256)
assert(arena_alloc(&arena, 128) != NULL);
assert(arena.tail->capacity == 256);
// Stress test: Allocate 10,000 small blocks
for (int i = 0; i < 10000; i++) {
void *ptr = arena_alloc(&arena, 7); // Odd size to test alignment scaling
assert(ptr != NULL);
assert(is_aligned(ptr));
}
// Verify regions were created
int regions = count_regions(&arena);
assert(regions > 3);
// Huge allocation bypassing standard growth
// Requesting 1MB when the next capacity might only be ~4KB
void *huge = arena_alloc(&arena, 1024 * 1024);
assert(huge != NULL);
assert(arena.tail->capacity >= 1024 * 1024);
arena_free(&arena);
}
static void test_snapshot_rewind_trim_logic(void)
{
printf("Running snapshot, rewind, and trim logic tests...\n");
Arena arena = arena_create(128);
// 1. Snapshot empty
ArenaMarker empty_marker = arena_snapshot(&arena);
assert(empty_marker.region == NULL);
// 2. Allocate across 3 regions
arena_alloc(&arena, 100); // Region 1 (Cap: 128)
arena_snapshot(&arena);
arena_alloc(&arena, 200); // Region 2 (Cap: 256)
ArenaMarker mark_r2 = arena_snapshot(&arena);
arena_alloc(&arena, 400); // Region 3 (Cap: 512)
assert(count_regions(&arena) == 3);
// 3. Rewind to Region 2
arena_rewind(&arena, mark_r2);
assert(arena.tail == mark_r2.region);
assert(arena.tail->count == mark_r2.count);
// Check that Region 3's count was reset to 0
assert(arena.tail->next != NULL);
assert(arena.tail->next->count == 0);
// 4. Trim trailing regions (Should drop Region 3)
arena_trim(&arena);
assert(arena.tail->next == NULL);
assert(count_regions(&arena) == 2);
// 5. Rewind to completely empty
arena_rewind(&arena, empty_marker);
assert(arena.tail == NULL || (arena.tail == arena.head && arena.tail->count == 0));
// Trim when virtually empty (should retain the head region if it exists)
arena_trim(&arena);
if (arena.head != NULL) {
assert(arena.head->next == NULL);
}
arena_free(&arena);
}
static void test_massive_string_formatting(void)
{
printf("Running massive string formatting tests...\n");
Arena arena = arena_create(1024);
// Create a 50,000 character string requirement
size_t massive_len = 50000;
char *massive_input = malloc(massive_len + 1);
assert(massive_input != NULL);
memset(massive_input, 'X', massive_len);
massive_input[massive_len] = '\0';
char *result = arena_sprintf(&arena, "PREFIX_%s_SUFFIX", massive_input);
assert(result != NULL);
size_t expected_length = 7 + massive_len + 7;
assert(strlen(result) == expected_length);
assert(strncmp(result, "PREFIX_XXX", 10) == 0);
assert(strcmp(result + expected_length - 7, "_SUFFIX") == 0);
free(massive_input);
arena_free(&arena);
}
static void test_zero_capacity_fallback(void)
{
printf("Running zero-capacity fallback tests...\n");
// Passing 0 should fall back to ARENA_REGION_CAPACITY implicitly on first alloc
Arena arena = arena_create(0);
void *ptr = arena_alloc(&arena, 10);
assert(ptr != NULL);
assert(arena.head->capacity >= ARENA_REGION_CAPACITY);
arena_free(&arena);
} }
int main(void) int main(void)
{ {
printf("struct Arena_Region {\n"); test_edge_case_inputs();
printf(" Arena_Region *next; (%zu)\n", offsetof(Arena_Region, next)); test_overflows();
printf(" size_t count; (%zu)\n", offsetof(Arena_Region, count)); test_geometric_growth_and_stress();
printf(" size_t capacity; (%zu)\n", offsetof(Arena_Region, capacity)); test_snapshot_rewind_trim_logic();
printf(" uint8_t data[]; (%zu)\n", offsetof(Arena_Region, data)); test_massive_string_formatting();
printf("};\n"); test_zero_capacity_fallback();
printf("Alignment: %zu\n", ARENA_ALIGNMENT); return EXIT_SUCCESS;
printf("================================================================\n");
Arena a = {0};
arena_alloc(&a, 4012);
arena_alloc(&a, 100);
arena_alloc(&a, 2301);
arena_alloc(&a, 4015);
arena_alloc(&a, 10000);
arena_reset(&a);
arena_alloc(&a, 1000);
arena_print(a);
char *str = arena_sprintf(&a, "Formatted string %x", 0xCAFE);
for (size_t i = 0; i < 1000; ++i) {
str = arena_sprintf(&a, "Formatted string %d", i);
}
printf("%s\n", str);
arena_print(a);
arena_free(&a);
return 0;
} }