From abea5cd8cd5a91099ae52c84aa2b389b4191cf28 Mon Sep 17 00:00:00 2001 From: Andrew Noyes Date: Fri, 15 Aug 2025 13:45:05 -0400 Subject: [PATCH] realloc_raw --- src/arena_allocator.cpp | 4 +-- src/arena_allocator.hpp | 47 +++++++++++++++++++++++++++++----- tests/test_arena_allocator.cpp | 22 ++++++++-------- 3 files changed, 54 insertions(+), 19 deletions(-) diff --git a/src/arena_allocator.cpp b/src/arena_allocator.cpp index d4642dd..ddb186b 100644 --- a/src/arena_allocator.cpp +++ b/src/arena_allocator.cpp @@ -65,8 +65,8 @@ void ArenaAllocator::reset() { current_offset_ = 0; } -void *ArenaAllocator::realloc(void *ptr, size_t old_size, size_t new_size, - size_t alignment) { +void *ArenaAllocator::realloc_raw(void *ptr, size_t old_size, size_t new_size, + size_t alignment) { if (ptr == nullptr) { return allocate_raw(new_size, alignment); } diff --git a/src/arena_allocator.hpp b/src/arena_allocator.hpp index ca81377..80f9dfb 100644 --- a/src/arena_allocator.hpp +++ b/src/arena_allocator.hpp @@ -232,11 +232,44 @@ public: * - If new_size == 0, returns nullptr (no deallocation occurs) * - If ptr is null, behaves like allocate(new_size, alignment) * - If ptr was the last allocation and space exists, extends in place - * - Otherwise allocates new space and copies min(old_size, new_size) bytes * - * ## Performance: - * - In-place extension: O(1) - just updates offset - * - Copy required: O(min(old_size, new_size)) for the memcpy + * ## Example: + * ```cpp + * void* ptr = arena.allocate(100); + * // ... use ptr ... + * ptr = arena.realloc_raw(ptr, 100, 200); // May extend in place or copy + * ``` + * + * ## Safety Notes: + * - The caller must provide the correct old_size - this is not tracked + * - The old pointer becomes invalid if a copy occurs + * - Like malloc/realloc, the contents beyond old_size are uninitialized + * - When copying to new location, uses the specified alignment + */ + void *realloc_raw(void *ptr, size_t old_size, size_t new_size, + size_t alignment = alignof(std::max_align_t)); + + /** + * @brief Reallocate memory, extending in place if possible or copying to a + * new location. + * + * This method provides realloc-like functionality for the arena allocator. + * If the given pointer was the last allocation and there's sufficient space + * in the current block to extend it, the allocation is grown in place. + * Otherwise, a new allocation is made and the old data is copied. + * + * @param ptr Pointer to the existing allocation (must be from this allocator) + * @param old_size Size of the existing allocation in T's + * @param new_size Desired new size in T's + * @return Pointer to the reallocated memory (may be the same as ptr or + * different) + * @throws std::bad_alloc if memory allocation fails + * + * ## Behavior: + * - If new_size == old_size, returns ptr unchanged + * - If new_size == 0, returns nullptr (no deallocation occurs) + * - If ptr is null, behaves like allocate(new_size, alignment) + * - If ptr was the last allocation and space exists, extends in place * * ## Example: * ```cpp @@ -251,8 +284,10 @@ public: * - Like malloc/realloc, the contents beyond old_size are uninitialized * - When copying to new location, uses the specified alignment */ - void *realloc(void *ptr, size_t old_size, size_t new_size, - size_t alignment = alignof(std::max_align_t)); + template T *realloc(T *ptr, size_t old_size, size_t new_size) { + return static_cast(realloc_raw(ptr, old_size * sizeof(T), + new_size * sizeof(T), alignof(T))); + } /** * @brief Construct an object of type T in the arena using placement new. diff --git a/tests/test_arena_allocator.cpp b/tests/test_arena_allocator.cpp index e6092f8..9f51424 100644 --- a/tests/test_arena_allocator.cpp +++ b/tests/test_arena_allocator.cpp @@ -369,7 +369,7 @@ TEST_CASE("ArenaAllocator realloc functionality") { size_t used_before = fresh_arena.used_bytes(); CHECK(used_before == 100); - void *result = fresh_arena.realloc(ptr, 100, 0); + void *result = fresh_arena.realloc_raw(ptr, 100, 0); CHECK(result == nullptr); CHECK(fresh_arena.used_bytes() == 0); // Memory should be reclaimed @@ -381,18 +381,18 @@ TEST_CASE("ArenaAllocator realloc functionality") { CHECK(used_before2 >= 100); // At least 100 bytes due to potential alignment void *result2 = - arena2.realloc(ptr1, 50, 0); // ptr1 is not the last allocation + arena2.realloc_raw(ptr1, 50, 0); // ptr1 is not the last allocation CHECK(result2 == nullptr); CHECK(arena2.used_bytes() == used_before2); // Memory should NOT be reclaimed // realloc with nullptr behaves like allocate - void *new_ptr = arena.realloc(nullptr, 0, 150); + void *new_ptr = arena.realloc_raw(nullptr, 0, 150); CHECK(new_ptr != nullptr); CHECK(arena.used_bytes() >= 150); // realloc with same size returns same pointer - void *same_ptr = arena.realloc(new_ptr, 150, 150); + void *same_ptr = arena.realloc_raw(new_ptr, 150, 150); CHECK(same_ptr == new_ptr); } @@ -405,7 +405,7 @@ TEST_CASE("ArenaAllocator realloc functionality") { std::memset(ptr, 0xAB, 100); // Should extend in place since it's the last allocation - void *extended_ptr = fresh_arena.realloc(ptr, 100, 200); + void *extended_ptr = fresh_arena.realloc_raw(ptr, 100, 200); CHECK(extended_ptr == ptr); // Should be same pointer (in-place) // Check that original data is preserved @@ -423,7 +423,7 @@ TEST_CASE("ArenaAllocator realloc functionality") { std::memset(ptr, 0xCD, 200); // Should shrink in place - void *shrunk_ptr = fresh_arena.realloc(ptr, 200, 100); + void *shrunk_ptr = fresh_arena.realloc_raw(ptr, 200, 100); CHECK(shrunk_ptr == ptr); // Same pointer CHECK(fresh_arena.used_bytes() == 100); @@ -447,7 +447,7 @@ TEST_CASE("ArenaAllocator realloc functionality") { // Try to reallocate ptr1 - should copy since ptr2 is now the last // allocation - void *realloc_ptr1 = fresh_arena.realloc(ptr1, 60, 120); + void *realloc_ptr1 = fresh_arena.realloc_raw(ptr1, 60, 120); CHECK(realloc_ptr1 != ptr1); // Should be different pointer (copy occurred) // Check that data was copied correctly @@ -458,7 +458,7 @@ TEST_CASE("ArenaAllocator realloc functionality") { // Try to reallocate ptr2 (last allocation) - should extend in place if // space allows - void *realloc_ptr2 = fresh_arena.realloc(ptr2, 30, 50); + void *realloc_ptr2 = fresh_arena.realloc_raw(ptr2, 30, 50); // Note: this might not be in-place if the realloc of ptr1 used up space in // the block Let's just check the result is valid and data is preserved CHECK(realloc_ptr2 != nullptr); @@ -477,7 +477,7 @@ TEST_CASE("ArenaAllocator realloc functionality") { std::memset(ptr, 0x33, 90); // Try to extend beyond block size - should copy to new block - void *extended_ptr = fresh_arena.realloc(ptr, 90, 150); + void *extended_ptr = fresh_arena.realloc_raw(ptr, 90, 150); CHECK(extended_ptr != ptr); // Should be different (new block) CHECK(fresh_arena.num_blocks() == 2); // Should have created new block @@ -497,7 +497,7 @@ TEST_CASE("ArenaAllocator realloc functionality") { std::memset(ptr, 0x44, 50); // Realloc with same alignment - should extend in place - void *extended_ptr = fresh_arena.realloc(ptr, 50, 100, 16); + void *extended_ptr = fresh_arena.realloc_raw(ptr, 50, 100, 16); CHECK(extended_ptr == ptr); // In place CHECK(reinterpret_cast(extended_ptr) % 16 == 0); @@ -520,7 +520,7 @@ TEST_CASE("ArenaAllocator realloc functionality") { // Grow in multiple steps for (size_t new_size = 100; new_size <= 500; new_size += 100) { - void *new_ptr = fresh_arena.realloc(ptr, current_size, new_size); + void *new_ptr = fresh_arena.realloc_raw(ptr, current_size, new_size); CHECK(new_ptr != nullptr); // Verify original data is still there