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_align64
|
||||
|
||||
6
Makefile
6
Makefile
@@ -1,6 +1,10 @@
|
||||
all: test test_align64
|
||||
|
||||
test: test.c arena.h
|
||||
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:
|
||||
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.
|
||||
//
|
||||
// [License and changelog]
|
||||
@@ -69,7 +69,8 @@
|
||||
// #define ARENA_ALIGNMENT alignment (alignof(max_align_t))
|
||||
//
|
||||
// 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)
|
||||
//
|
||||
@@ -97,8 +98,9 @@
|
||||
//
|
||||
// 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 consider memory
|
||||
// alignment.
|
||||
// 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]
|
||||
//
|
||||
@@ -177,6 +179,11 @@
|
||||
// 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.
|
||||
@@ -225,9 +232,10 @@
|
||||
|
||||
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;
|
||||
alignas(ARENA_ALIGNMENT) uint8_t data[];
|
||||
uint8_t storage[];
|
||||
} Arena__Region;
|
||||
|
||||
typedef struct {
|
||||
@@ -272,6 +280,7 @@ void arena_rewind(Arena *arena, ArenaMarker marker);
|
||||
|
||||
// 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)
|
||||
{
|
||||
@@ -351,21 +360,10 @@ void *arena_alloc(Arena *arena, size_t size)
|
||||
? aligned_size
|
||||
: default_capacity);
|
||||
|
||||
if (alloc_capacity > SIZE_MAX - sizeof(Arena__Region)) {
|
||||
ARENA_ASSERT(false && "Region allocation size overflow");
|
||||
return NULL;
|
||||
}
|
||||
arena->head = arena->tail = arena__region_create(alloc_capacity);
|
||||
if (arena->head == 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->capacity = alloc_capacity;
|
||||
return arena->head->data;
|
||||
}
|
||||
|
||||
@@ -396,22 +394,11 @@ void *arena_alloc(Arena *arena, size_t size)
|
||||
? 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;
|
||||
}
|
||||
last_region->next = arena__region_create(alloc_capacity);
|
||||
if (last_region->next == NULL) { 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;
|
||||
}
|
||||
|
||||
@@ -531,7 +518,19 @@ void arena_rewind(Arena *arena, ArenaMarker marker)
|
||||
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;
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
40
test.c
40
test.c
@@ -160,6 +160,45 @@ static void test_snapshot_rewind_trim_logic(void)
|
||||
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)
|
||||
{
|
||||
printf("Running massive string formatting tests...\n");
|
||||
@@ -204,6 +243,7 @@ int main(void)
|
||||
test_overflows();
|
||||
test_geometric_growth_and_stress();
|
||||
test_snapshot_rewind_trim_logic();
|
||||
test_invalid_markers();
|
||||
test_massive_string_formatting();
|
||||
test_zero_capacity_fallback();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user