# HG changeset patch # User Mike Becker # Date 1764949097 -3600 # Node ID 197450c2b0b39c8f9c8030ba0fcf55872544f7e3 # Parent d06aa9db04084d754f42ed57e8b35ed1416fc45e change cxBufferReserve() to allow reducing the capacity resolves #773 diff -r d06aa9db0408 -r 197450c2b0b3 CHANGELOG --- a/CHANGELOG Fri Dec 05 16:36:10 2025 +0100 +++ b/CHANGELOG Fri Dec 05 16:38:17 2025 +0100 @@ -2,6 +2,7 @@ ------------------------ * adds cx_system_page_size() to allocator.h + * change cxBufferReserve() to allow reducing the capacity * fix that cxReallocate(), cxReallocateArray(), cx_reallocate(), and cx_reallocatearray() were not returning zero after freeing the memory when passed a size of zero diff -r d06aa9db0408 -r 197450c2b0b3 docs/Writerside/topics/about.md --- a/docs/Writerside/topics/about.md Fri Dec 05 16:36:10 2025 +0100 +++ b/docs/Writerside/topics/about.md Fri Dec 05 16:38:17 2025 +0100 @@ -29,6 +29,7 @@ ### Version 4.0 - preview {collapsible="true"} * adds cx_system_page_size() to allocator.h +* change cxBufferReserve() to allow reducing the capacity * fix that cxReallocate(), cxReallocateArray(), cx_reallocate(), and cx_reallocatearray() were not returning zero after freeing the memory when passed a size of zero diff -r d06aa9db0408 -r 197450c2b0b3 docs/Writerside/topics/buffer.h.md --- a/docs/Writerside/topics/buffer.h.md Fri Dec 05 16:36:10 2025 +0100 +++ b/docs/Writerside/topics/buffer.h.md Fri Dec 05 16:38:17 2025 +0100 @@ -129,12 +129,14 @@ void cxBufferShrink(CxBuffer *buffer, size_t reserve); ``` -The functions `cxBufferReserve()` and `cxBufferMinimumCapacity()` guarantee a buffer capacity of _at least_ `capacity`. -The difference is, that `cxBufferReserve()` will not increase the capacity beyond the specified `capacity`, -while `cxBufferMinimumCapacity()` will grow the capacity in powers of two until the system's page size is reached. +The function `cxBufferMinimumCapacity()` guarantees a buffer capacity of _at least_ `capacity`. +It will grow the capacity in powers of two until the system's page size is reached. Then, the new capacity will be a multiple of the page size. The function returns non-zero if increasing the capacity was attempted unsuccessfully. +The function `cxBufferReserve()`, on the other hand, will reallocate the buffer's space to match exactly the requested `capacity` +and the contents are truncated if required. + You should use `cxBufferReserve()` when you know precisely the required capacity beforehand and `cxBufferMinimumCapacity()` when it is likely that the buffer needs to regrow soon. diff -r d06aa9db0408 -r 197450c2b0b3 src/buffer.c --- a/src/buffer.c Fri Dec 05 16:36:10 2025 +0100 +++ b/src/buffer.c Fri Dec 05 16:38:17 2025 +0100 @@ -210,7 +210,7 @@ } int cxBufferReserve(CxBuffer *buffer, size_t newcap) { - if (newcap <= buffer->capacity) { + if (newcap == buffer->capacity) { return 0; } const int force_copy_flags = CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_COPY_ON_EXTEND; @@ -225,7 +225,11 @@ return 0; } else if (cxReallocate(buffer->allocator, (void **) &buffer->bytes, newcap) == 0) { + buffer->flags |= CX_BUFFER_FREE_CONTENTS; buffer->capacity = newcap; + if (buffer->size > newcap) { + buffer->size = newcap; + } return 0; } else { return -1; // LCOV_EXCL_LINE diff -r d06aa9db0408 -r 197450c2b0b3 src/cx/buffer.h --- a/src/cx/buffer.h Fri Dec 05 16:36:10 2025 +0100 +++ b/src/cx/buffer.h Fri Dec 05 16:38:17 2025 +0100 @@ -443,6 +443,8 @@ * Ensures that the buffer has the required capacity. * * If the current capacity is not sufficient, the buffer will be extended. + * If the current capacity is larger, the buffer is shrunk and superfluous + * content is discarded. * * This function will reserve no more bytes than requested, in contrast to * cxBufferMinimumCapacity(), which may reserve more bytes to improve the @@ -450,7 +452,7 @@ * * @param buffer the buffer * @param capacity the required capacity for this buffer - * @retval zero the capacity was already sufficient or successfully increased + * @retval zero on success * @retval non-zero on allocation failure * @see cxBufferShrink() * @see cxBufferMinimumCapacity() diff -r d06aa9db0408 -r 197450c2b0b3 tests/test_buffer.c --- a/tests/test_buffer.c Fri Dec 05 16:36:10 2025 +0100 +++ b/tests/test_buffer.c Fri Dec 05 16:38:17 2025 +0100 @@ -277,6 +277,41 @@ cx_testing_allocator_destroy(&talloc); } +CX_TEST(test_buffer_reserve) { + CxTestingAllocator talloc; + cx_testing_allocator_init(&talloc); + CxAllocator *alloc = &talloc.base; + CX_TEST_DO { + CxBuffer buf; + char space[16] = "Testing"; + cxBufferInit(&buf, space, 16, alloc, CX_BUFFER_COPY_ON_EXTEND); + buf.size = 8; + CX_TEST_ASSERT(buf.capacity == 16); + CX_TEST_ASSERT(talloc.alloc_total == 0); + // reserve to grow + cxBufferReserve(&buf, 32); + CX_TEST_ASSERT(buf.capacity == 32); + CX_TEST_ASSERT(buf.size == 8); + CX_TEST_ASSERT(memcmp(buf.space, "Testing", 8) == 0); + CX_TEST_ASSERT(talloc.alloc_total > 0); + CX_TEST_ASSERT((buf.flags & CX_BUFFER_COPY_ON_EXTEND) == 0); + // reserve to shrink + buf.size = 24; + cxBufferReserve(&buf, 16); + CX_TEST_ASSERT(buf.capacity == 16); + CX_TEST_ASSERT(buf.size == 16); + CX_TEST_ASSERT(memcmp(buf.space, "Testing", 8) == 0); + // reserve to free + cxBufferReserve(&buf, 0); + CX_TEST_ASSERT(buf.capacity == 0); + CX_TEST_ASSERT(buf.size == 0); + CX_TEST_ASSERT(buf.space == NULL); + CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); + cxBufferDestroy(&buf); + } + cx_testing_allocator_destroy(&talloc); +} + CX_TEST(test_buffer_clear) { char space[16]; strcpy(space, "clear test"); @@ -1769,6 +1804,7 @@ cx_test_register(suite, test_buffer_shrink); cx_test_register(suite, test_buffer_shrink_copy_on_write); cx_test_register(suite, test_buffer_shrink_copy_on_extend); + cx_test_register(suite, test_buffer_reserve); cx_test_register(suite, test_buffer_clear); cx_test_register(suite, test_buffer_clear_copy_on_write); cx_test_register(suite, test_buffer_reset);