From 4cfd4fd5f96fe2b21f4a08f49ec9307e61d5b138 Mon Sep 17 00:00:00 2001 From: Cristian Gora Date: Sun, 20 Sep 2026 18:52:14 +0200 Subject: [PATCH] v1.2.4 - Major rewrite --- .clangd | 16 ++ LICENSE | 2 +- Makefile | 2 +- arena.h | 548 ++++++++++++++++++++++++++++--------------------------- test.c | 249 +++++++++++++++++++------ 5 files changed, 495 insertions(+), 322 deletions(-) create mode 100644 .clangd diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..f1f05c7 --- /dev/null +++ b/.clangd @@ -0,0 +1,16 @@ +CompileFlags: + Add: + - "-Wall" + - "-Wextra" + - "-pedantic" + - "-std=c11" + +--- + +If: + PathMatch: arena\.h + +CompileFlags: + Add: + - "-xc-header" + - "-DARENA_IMPLEMENTATION" diff --git a/LICENSE b/LICENSE index bfa2314..2454f65 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 seajee +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 diff --git a/Makefile b/Makefile index 1cd4d09..2d67a57 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ test: test.c arena.h - cc -Wall -Wextra -DDEBUG -o test test.c + clang -Wall -Wextra -pedantic -std=c11 -o test test.c clean: rm -rf test diff --git a/arena.h b/arena.h index 0c9b172..7d392e0 100644 --- a/arena.h +++ b/arena.h @@ -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. // // [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. @@ -16,10 +48,28 @@ // #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 -// 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) // @@ -30,17 +80,12 @@ // This macro defines the growth factor of the capacity for every // 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 // -// This macro disables assertions that happen when allocations fail. -// The disabled assertions will be replaced with a simple condition -// that returns NULL when it fails. This macro affects every function -// that returns a pointer. +// 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 // @@ -51,116 +96,106 @@ // #define ARENA_FREE my_free // // 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 // alignment. // // [Function documentation] // -// Note: every function that starts with "arena__" is considered a 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 +// 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 [Example] section): initializes an arena -// with a specified region capacity. It will override the default -// ARENA_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 *a) +// void arena_free(Arena *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 -// still be reused. If the arena was created with arena_create() the -// configured region_capacity will be retained. +// 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 *a) +// 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 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 // ideal behaviour. // -// void *arena_alloc(Arena *a, size_t size) +// void *arena_alloc(Arena *arena, size_t size) // // This function allocates a buffer of 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 * 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. // -// 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 bytes into the arena and copies // the contents of . 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 -// the specified arena. It duplicates a string in the arena. +// 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 *a, const char *fmt, ...) +// char *arena_sprintf(Arena *arena, const char *fmt, ...) // -// This function internally uses vsnprintf() to store the resulting formatted -// string in the specified arena. Returns NULL on error. +// 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. // -// 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 // 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 *a, Arena_Marker marker) +// 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() +// // [Notes] // -// This library is not Thread-safe. -// -// [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] +// This library does not account for thread safety. #ifndef ARENA_H_ #define ARENA_H_ #include #include +#include #include #include #include #include +#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 @@ -169,10 +204,6 @@ int main(void) # define ARENA_REGION_GROWTH_FACTOR 2 #endif // ARENA_REGION_GROWTH_FACTOR -#ifndef ARENA_ALIGNMENT -# define ARENA_ALIGNMENT alignof(max_align_t) -#endif // ARENA_ALIGNMENT - #ifndef ARENA_NO_ASSERT # ifndef ARENA_ASSERT # include @@ -192,49 +223,44 @@ int main(void) # define ARENA_FREE free #endif // ARENA_FREE -#ifdef __cplusplus -extern "C" { // Prevent name mangling of functions -#endif // __cplusplus - -typedef struct Arena_Region Arena_Region; - -struct Arena_Region { - Arena_Region *next; +typedef struct Arena_Region { + struct Arena_Region *next; size_t count; size_t capacity; alignas(ARENA_ALIGNMENT) uint8_t data[]; -}; +} Arena__Region; typedef struct { - Arena_Region *region; - size_t count; -} Arena_Marker; - -typedef struct Arena { - Arena_Region *head; - Arena_Region *tail; + 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 *a); -void arena_reset(Arena *a); -void arena_trim(Arena *a); +void arena_free(Arena *arena); +void arena_reset(Arena *arena); +void arena_trim(Arena *arena); // Allocation and other utilities -void *arena_alloc(Arena *a, size_t size); -void *arena_calloc(Arena *a, size_t count, size_t size); -void *arena_copy(Arena *a, const void *src, size_t size); -char *arena_strdup(Arena *a, const char *s); -char *arena_sprintf(Arena *a, const char *fmt, ...); +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 -Arena_Marker arena_snapshot(const Arena *a); -void arena_rewind(Arena *a, Arena_Marker marker); - -// Private functions -size_t arena__align(size_t n); +ArenaMarker arena_snapshot(const Arena *arena); +void arena_rewind(Arena *arena, ArenaMarker marker); #ifdef __cplusplus } @@ -244,197 +270,203 @@ size_t arena__align(size_t n); #ifdef ARENA_IMPLEMENTATION -#ifdef __cplusplus -extern "C" { // Prevent name mangling of functions -#endif // __cplusplus +// Private functions +static size_t arena__align(size_t n); Arena arena_create(size_t region_capacity) { - Arena a; - memset(&a, 0, sizeof(a)); - a.region_capacity = region_capacity; - return a; + Arena arena; + memset(&arena, 0, sizeof(arena)); + + arena.region_capacity = region_capacity; + return arena; } -void arena_free(Arena *a) +void arena_free(Arena *arena) { - if (a == NULL) { + if (arena == NULL) { return; } - Arena_Region *cur = a->head; - while (cur != NULL) { - Arena_Region *next = cur->next; - ARENA_FREE(cur); - cur = next; + Arena__Region *current = arena->head; + + while (current != NULL) { + Arena__Region *next = current->next; + ARENA_FREE(current); + current = next; } - a->head = NULL; - a->tail = NULL; + arena->head = 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; } - for (Arena_Region *cur = a->head; cur != NULL; cur = cur->next) { - cur->count = 0; + for (Arena__Region *r = arena->head; r != NULL; r = r->next) { + 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; } - Arena_Region *to_free = a->tail->next; - a->tail->next = NULL; + Arena__Region *to_free = arena->tail->next; + arena->tail->next = NULL; while (to_free != NULL) { - Arena_Region *next = to_free->next; + Arena__Region *next = to_free->next; ARENA_FREE(to_free); 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"); return NULL; } - // Check for overflow - if (size > SIZE_MAX - sizeof(Arena_Region)) { - ARENA_ASSERT(false && "Requested size too large"); - 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"); + 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 (a->head == NULL) { - a->head = (Arena_Region*)ARENA_MALLOC(sizeof(*a->head) + alloc_size); - if (a->head == NULL) { - ARENA_ASSERT(false && "Allocation failed"); + 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); + + if (alloc_capacity > SIZE_MAX - sizeof(Arena__Region)) { + ARENA_ASSERT(false && "Region allocation size overflow"); return NULL; } - a->head->next = NULL; - a->head->count = size; - a->head->capacity = alloc_size; - a->tail = a->head; - 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"); + arena->head = arena->tail = (Arena__Region *)ARENA_MALLOC( + sizeof(*arena->head) + alloc_capacity); + if (arena->head == NULL) { + ARENA_ASSERT(false && "Memory allocation failed"); return NULL; } - a->tail = r; - r->count += size; - return r->data + r->count - size; + + arena->head->next = NULL; + arena->head->count = aligned_size; + arena->head->capacity = alloc_capacity; + return arena->head->data; } - // 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"); + // 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; } - a->tail = last->next; - a->tail->next = NULL; - a->tail->count = size; - a->tail->capacity = alloc_size; - return a->tail->data; + 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 *a, size_t count, size_t size) +void *arena_calloc(Arena *arena, size_t count, size_t size) { - if (a == NULL) { + if (arena == NULL) { ARENA_ASSERT(false && "Invalid parameters"); return NULL; } - void *result = arena_alloc(a, count * size); - - if (result != NULL) { - memset(result, 0, count * size); + // Prevent size_t overflow during arena_alloc size calculation + if (size > 0 && count > SIZE_MAX / size) { + ARENA_ASSERT(false && "Allocation size overflow"); + 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"); return NULL; } - void *copy = arena_alloc(a, size); - if (copy == NULL) { - 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_strdup(Arena *a, const char *s) +char *arena_sprintf(Arena *arena, const char *fmt, ...) { - if (a == NULL || s == 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) { + if (arena == NULL || fmt == NULL) { ARENA_ASSERT(false && "Invalid parameters"); return NULL; } @@ -442,105 +474,83 @@ char *arena_sprintf(Arena *a, const char *fmt, ...) va_list args; 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_copy(args_copy, args); - int n = vsnprintf(result, available, fmt, args_copy); + int length = vsnprintf(NULL, 0, fmt, args_copy); va_end(args_copy); - - if (n < 0) { + if (length < 0) { + ARENA_ASSERT(false && "Format string length calculation failed"); va_end(args); return NULL; } - size_t needed = (size_t)n + 1; - - // If it fits commit and return - 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) { + 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; } - n = vsnprintf(result, needed, fmt, args); va_end(args); - - if (n < 0) { - return NULL; - } - - return result; + 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"); return marker; } - marker.region = a->tail; - marker.count = (a->tail != NULL ? a->tail->count : 0); - + marker.region = arena->tail; + marker.count = (arena->tail != NULL ? arena->tail->count : 0); 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"); return; } // If arena was empty reset it if (marker.region == NULL) { - arena_reset(a); + arena_reset(arena); return; } - a->tail = marker.region; - a->tail->count = marker.count; + arena->tail = marker.region; + arena->tail->count = marker.count; - // Clear new regions in case they were appended - for (Arena_Region *cur = a->tail->next; cur != NULL; cur = cur->next) { - cur->count = 0; + // Reset any new regions that were appended + for (Arena__Region *r = arena->tail->next; r != NULL; r = r->next) { + 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); } -#ifdef __cplusplus -} -#endif // __cplusplus - #endif // ARENA_IMPLEMENTATION /* * Revision history: * + * 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 @@ -562,7 +572,7 @@ size_t arena__align(size_t n) /* * MIT License * - * Copyright (c) 2025 seajee + * 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"), diff --git a/test.c b/test.c index f9a8849..4db627e 100644 --- a/test.c +++ b/test.c @@ -1,64 +1,211 @@ +#include +#include #include -#include - -#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 +#include #define ARENA_IMPLEMENTATION -#define ARENA_REGION_GROWTH_FACTOR 2 +#define ARENA_NO_ASSERT #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; - for (Arena_Region *cur = arena.head; cur != NULL; cur = cur->next) { - printf("Arena_Region #%d:\n", ++i); - printf(" count = %ld\n", cur->count); - printf(" capacity = %ld\n", cur->capacity); - printf(" next = %p\n", cur->next); - printf(" data = %p\n", cur->data); + int count = 0; + for (Arena__Region *r = arena->head; r != NULL; r = r->next) { + count++; } + 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) { - printf("struct Arena_Region {\n"); - printf(" Arena_Region *next; (%zu)\n", offsetof(Arena_Region, next)); - printf(" size_t count; (%zu)\n", offsetof(Arena_Region, count)); - printf(" size_t capacity; (%zu)\n", offsetof(Arena_Region, capacity)); - printf(" uint8_t data[]; (%zu)\n", offsetof(Arena_Region, data)); - printf("};\n"); + test_edge_case_inputs(); + test_overflows(); + test_geometric_growth_and_stress(); + test_snapshot_rewind_trim_logic(); + test_massive_string_formatting(); + test_zero_capacity_fallback(); - printf("Alignment: %zu\n", ARENA_ALIGNMENT); - 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; + return EXIT_SUCCESS; }