v1.2.5 - Fix alignment above alignof(max_align_t) and stale marker rewinds
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
test
|
test
|
||||||
|
test_align64
|
||||||
|
|||||||
6
Makefile
6
Makefile
@@ -1,6 +1,10 @@
|
|||||||
|
all: test test_align64
|
||||||
|
|
||||||
test: test.c arena.h
|
test: test.c arena.h
|
||||||
clang -Wall -Wextra -pedantic -std=c11 -o test test.c
|
clang -Wall -Wextra -pedantic -std=c11 -o test test.c
|
||||||
|
|
||||||
|
test_align64: test.c arena.h
|
||||||
|
clang -Wall -Wextra -pedantic -std=c11 -DARENA_ALIGNMENT=64 -o test_align64 test.c
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf test
|
rm -rf test test_align64
|
||||||
|
|||||||
93
arena.h
93
arena.h
@@ -1,4 +1,4 @@
|
|||||||
// arena.h - v1.2.4 - MIT License
|
// arena.h - v1.2.5 - MIT License
|
||||||
// single header library for region-based memory management.
|
// single header library for region-based memory management.
|
||||||
//
|
//
|
||||||
// [License and changelog]
|
// [License and changelog]
|
||||||
@@ -69,7 +69,8 @@
|
|||||||
// #define ARENA_ALIGNMENT alignment (alignof(max_align_t))
|
// #define ARENA_ALIGNMENT alignment (alignof(max_align_t))
|
||||||
//
|
//
|
||||||
// This macro defines the memory alignment used for allocations.
|
// This macro defines the memory alignment used for allocations.
|
||||||
// Must be a power of two.
|
// 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)
|
// #define ARENA_REGION_CAPACITY new_region_capacity_in_bytes (8*1024)
|
||||||
//
|
//
|
||||||
@@ -97,8 +98,9 @@
|
|||||||
//
|
//
|
||||||
// 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 return memory
|
||||||
// alignment.
|
// aligned at least like malloc() does. ARENA_ALIGNMENT is handled by
|
||||||
|
// the library itself.
|
||||||
//
|
//
|
||||||
// [Function documentation]
|
// [Function documentation]
|
||||||
//
|
//
|
||||||
@@ -177,6 +179,11 @@
|
|||||||
// Note: this function does not free any newly allocated regions internally.
|
// Note: this function does not free any newly allocated regions internally.
|
||||||
// See arena_trim()
|
// 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]
|
// [Notes]
|
||||||
//
|
//
|
||||||
// This library does not account for thread safety.
|
// This library does not account for thread safety.
|
||||||
@@ -225,9 +232,10 @@
|
|||||||
|
|
||||||
typedef struct Arena_Region {
|
typedef struct Arena_Region {
|
||||||
struct Arena_Region *next;
|
struct Arena_Region *next;
|
||||||
|
uint8_t *data; // Start of user buffers, aligned to ARENA_ALIGNMENT
|
||||||
size_t count;
|
size_t count;
|
||||||
size_t capacity;
|
size_t capacity;
|
||||||
alignas(ARENA_ALIGNMENT) uint8_t data[];
|
uint8_t storage[];
|
||||||
} Arena__Region;
|
} Arena__Region;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -272,6 +280,7 @@ void arena_rewind(Arena *arena, ArenaMarker marker);
|
|||||||
|
|
||||||
// Private functions
|
// Private functions
|
||||||
static size_t arena__align(size_t n);
|
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_create(size_t region_capacity)
|
||||||
{
|
{
|
||||||
@@ -351,21 +360,10 @@ void *arena_alloc(Arena *arena, size_t size)
|
|||||||
? aligned_size
|
? aligned_size
|
||||||
: default_capacity);
|
: default_capacity);
|
||||||
|
|
||||||
if (alloc_capacity > SIZE_MAX - sizeof(Arena__Region)) {
|
arena->head = arena->tail = arena__region_create(alloc_capacity);
|
||||||
ARENA_ASSERT(false && "Region allocation size overflow");
|
if (arena->head == NULL) { return NULL; }
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
arena->head->next = NULL;
|
|
||||||
arena->head->count = aligned_size;
|
arena->head->count = aligned_size;
|
||||||
arena->head->capacity = alloc_capacity;
|
|
||||||
return arena->head->data;
|
return arena->head->data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -396,22 +394,11 @@ void *arena_alloc(Arena *arena, size_t size)
|
|||||||
? aligned_size
|
? aligned_size
|
||||||
: next_capacity);
|
: next_capacity);
|
||||||
|
|
||||||
if (alloc_capacity > SIZE_MAX - sizeof(Arena__Region)) {
|
last_region->next = arena__region_create(alloc_capacity);
|
||||||
ARENA_ASSERT(false && "Region allocation size overflow");
|
if (last_region->next == NULL) { return NULL; }
|
||||||
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 = last_region->next;
|
||||||
arena->tail->next = NULL;
|
|
||||||
arena->tail->count = aligned_size;
|
arena->tail->count = aligned_size;
|
||||||
arena->tail->capacity = alloc_capacity;
|
|
||||||
return arena->tail->data;
|
return arena->tail->data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,7 +518,19 @@ void arena_rewind(Arena *arena, ArenaMarker marker)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
arena->tail = marker.region;
|
// 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;
|
arena->tail->count = marker.count;
|
||||||
|
|
||||||
// Reset any new regions that were appended
|
// Reset any new regions that were appended
|
||||||
@@ -545,11 +544,41 @@ static size_t arena__align(size_t n)
|
|||||||
return (n + (ARENA_ALIGNMENT - 1)) & ~(ARENA_ALIGNMENT - 1);
|
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
|
#endif // ARENA_IMPLEMENTATION
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Revision history:
|
* 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.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;
|
||||||
|
|||||||
40
test.c
40
test.c
@@ -160,6 +160,45 @@ static void test_snapshot_rewind_trim_logic(void)
|
|||||||
arena_free(&arena);
|
arena_free(&arena);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_invalid_markers(void)
|
||||||
|
{
|
||||||
|
printf("Running invalid marker tests...\n");
|
||||||
|
Arena arena = arena_create(128);
|
||||||
|
|
||||||
|
void *first = arena_alloc(&arena, 100); // Region 1 (Cap: 128)
|
||||||
|
assert(is_aligned(first));
|
||||||
|
ArenaMarker mark_r1 = arena_snapshot(&arena);
|
||||||
|
|
||||||
|
arena_alloc(&arena, 200); // Region 2 (Cap: 256)
|
||||||
|
ArenaMarker mark_r2 = arena_snapshot(&arena);
|
||||||
|
|
||||||
|
// Free Region 2, invalidating mark_r2
|
||||||
|
arena_rewind(&arena, mark_r1);
|
||||||
|
arena_trim(&arena);
|
||||||
|
assert(count_regions(&arena) == 1);
|
||||||
|
|
||||||
|
// Stale marker must be rejected without touching freed memory
|
||||||
|
arena_rewind(&arena, mark_r2);
|
||||||
|
assert(arena.tail == mark_r1.region);
|
||||||
|
assert(arena.tail->count == mark_r1.count);
|
||||||
|
|
||||||
|
// Marker from another arena
|
||||||
|
Arena other = arena_create(128);
|
||||||
|
arena_alloc(&other, 10);
|
||||||
|
arena_rewind(&arena, arena_snapshot(&other));
|
||||||
|
assert(arena.tail == mark_r1.region);
|
||||||
|
assert(arena.tail->count == mark_r1.count);
|
||||||
|
arena_free(&other);
|
||||||
|
|
||||||
|
// Marker with a count beyond the region capacity
|
||||||
|
ArenaMarker forged = mark_r1;
|
||||||
|
forged.count = mark_r1.region->capacity + 1;
|
||||||
|
arena_rewind(&arena, forged);
|
||||||
|
assert(arena.tail->count == mark_r1.count);
|
||||||
|
|
||||||
|
arena_free(&arena);
|
||||||
|
}
|
||||||
|
|
||||||
static void test_massive_string_formatting(void)
|
static void test_massive_string_formatting(void)
|
||||||
{
|
{
|
||||||
printf("Running massive string formatting tests...\n");
|
printf("Running massive string formatting tests...\n");
|
||||||
@@ -204,6 +243,7 @@ int main(void)
|
|||||||
test_overflows();
|
test_overflows();
|
||||||
test_geometric_growth_and_stress();
|
test_geometric_growth_and_stress();
|
||||||
test_snapshot_rewind_trim_logic();
|
test_snapshot_rewind_trim_logic();
|
||||||
|
test_invalid_markers();
|
||||||
test_massive_string_formatting();
|
test_massive_string_formatting();
|
||||||
test_zero_capacity_fallback();
|
test_zero_capacity_fallback();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user