Tue, 09 Sep 2025 20:56:47 +0200
update ucx
--- a/dav/assistant.c Tue Sep 09 16:01:30 2025 +0200 +++ b/dav/assistant.c Tue Sep 09 20:56:47 2025 +0200 @@ -31,7 +31,6 @@ #include <string.h> #include <cx/string.h> -#include <cx/utils.h> #include <libidav/utils.h>
--- a/dav/config.c Tue Sep 09 16:01:30 2025 +0200 +++ b/dav/config.c Tue Sep 09 20:56:47 2025 +0200 @@ -31,7 +31,7 @@ #include <string.h> #include <sys/types.h> #include <cx/hash_map.h> -#include <cx/utils.h> +#include <cx/streams.h> #include <errno.h> #include <libxml/tree.h>
--- a/dav/connect.c Tue Sep 09 16:01:30 2025 +0200 +++ b/dav/connect.c Tue Sep 09 20:56:47 2025 +0200 @@ -37,7 +37,6 @@ #include <libidav/utils.h> #include <cx/string.h> -#include <cx/utils.h> DavSession* connect_to_repo(DavContext *ctx, DavCfgRepository *repo, const char *path, dav_auth_func authfunc, CmdArgs *a) { cxmutstr decodedpw = dav_repository_get_decodedpassword(repo);
--- a/dav/db.c Tue Sep 09 16:01:30 2025 +0200 +++ b/dav/db.c Tue Sep 09 20:56:47 2025 +0200 @@ -33,7 +33,6 @@ #include "db.h" -#include <cx/utils.h> #include <cx/array_list.h> #include <libidav/utils.h>
--- a/dav/main.c Tue Sep 09 16:01:30 2025 +0200 +++ b/dav/main.c Tue Sep 09 20:56:47 2025 +0200 @@ -39,10 +39,10 @@ #include <unistd.h> #endif #include <cx/string.h> -#include <cx/utils.h> #include <cx/printf.h> #include <cx/hash_map.h> #include <cx/linked_list.h> +#include <cx/streams.h> #include <libidav/utils.h>
--- a/dav/scfg.c Tue Sep 09 16:01:30 2025 +0200 +++ b/dav/scfg.c Tue Sep 09 20:56:47 2025 +0200 @@ -32,7 +32,6 @@ #include <errno.h> #include <libidav/utils.h> #include <cx/hash_map.h> -#include <cx/utils.h> #include <cx/linked_list.h> #include <cx/printf.h>
--- a/dav/sync.c Tue Sep 09 16:01:30 2025 +0200 +++ b/dav/sync.c Tue Sep 09 20:56:47 2025 +0200 @@ -36,10 +36,10 @@ #include <libxml/xmlerror.h> #include <sys/types.h> #include <cx/map.h> -#include <cx/utils.h> #include <cx/list.h> #include <cx/hash_map.h> #include <cx/printf.h> +#include <cx/streams.h> #ifndef _WIN32 // unix includes
--- a/dav/tags.c Tue Sep 09 16:01:30 2025 +0200 +++ b/dav/tags.c Tue Sep 09 20:56:47 2025 +0200 @@ -32,7 +32,6 @@ #include <ctype.h> #include <cx/string.h> -#include <cx/utils.h> #include <cx/printf.h> #include <cx/hash_map.h>
--- a/libidav/davqlexec.c Tue Sep 09 16:01:30 2025 +0200 +++ b/libidav/davqlexec.c Tue Sep 09 20:56:47 2025 +0200 @@ -31,7 +31,6 @@ #include <string.h> #include <inttypes.h> -#include <cx/utils.h> #include <cx/map.h> #include <cx/hash_map.h> #include <cx/printf.h>
--- a/libidav/davqlparser.c Tue Sep 09 16:01:30 2025 +0200 +++ b/libidav/davqlparser.c Tue Sep 09 20:56:47 2025 +0200 @@ -27,7 +27,6 @@ */ #include "davqlparser.h" -#include <cx/utils.h> #include <cx/linked_list.h> #include <cx/printf.h> #include <string.h>
--- a/libidav/methods.c Tue Sep 09 16:01:30 2025 +0200 +++ b/libidav/methods.c Tue Sep 09 20:56:47 2025 +0200 @@ -36,7 +36,6 @@ #include "session.h" #include "xml.h" -#include <cx/utils.h> #include <cx/printf.h> #include <cx/hash_map.h>
--- a/libidav/pwdstore.c Tue Sep 09 16:01:30 2025 +0200 +++ b/libidav/pwdstore.c Tue Sep 09 20:56:47 2025 +0200 @@ -34,8 +34,8 @@ #include <stdlib.h> #include <string.h> -#include <cx/utils.h> #include <cx/hash_map.h> +#include <cx/streams.h> #ifdef _WIN32 #include <winsock.h>
--- a/libidav/resource.c Tue Sep 09 16:01:30 2025 +0200 +++ b/libidav/resource.c Tue Sep 09 20:56:47 2025 +0200 @@ -37,7 +37,6 @@ #include "methods.h" #include "crypto.h" #include <cx/buffer.h> -#include <cx/utils.h> #include <cx/hash_map.h> #include <cx/printf.h> #include <cx/mempool.h>
--- a/libidav/utils.c Tue Sep 09 16:01:30 2025 +0200 +++ b/libidav/utils.c Tue Sep 09 20:56:47 2025 +0200 @@ -34,7 +34,6 @@ #include <ctype.h> #include <cx/string.h> #include <cx/buffer.h> -#include <cx/utils.h> #include <cx/printf.h> #include <libxml/tree.h> #include <curl/curl.h>
--- a/libidav/webdav.c Tue Sep 09 16:01:30 2025 +0200 +++ b/libidav/webdav.c Tue Sep 09 20:56:47 2025 +0200 @@ -36,7 +36,7 @@ #include "session.h" #include "methods.h" #include <cx/buffer.h> -#include <cx/utils.h> +#include <cx/printf.h> #include <cx/linked_list.h> #include <cx/hash_map.h> #include <cx/compare.h>
--- a/libidav/xml.c Tue Sep 09 16:01:30 2025 +0200 +++ b/libidav/xml.c Tue Sep 09 20:56:47 2025 +0200 @@ -30,7 +30,6 @@ #include <stdlib.h> #include <string.h> -#include <cx/utils.h> #include <cx/printf.h> #include "xml.h"
--- a/test/crypto.c Tue Sep 09 16:01:30 2025 +0200 +++ b/test/crypto.c Tue Sep 09 20:56:47 2025 +0200 @@ -34,7 +34,6 @@ #include "crypto.h" #include <cx/string.h> -#include <cx/utils.h> #include <cx/buffer.h> #include <libidav/utils.h> #include <libidav/crypto.h>
--- a/ucx/allocator.c Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/allocator.c Tue Sep 09 20:56:47 2025 +0200 @@ -29,6 +29,7 @@ #include "cx/allocator.h" #include <errno.h> +#include <string.h> static void *cx_malloc_stdlib( cx_attr_unused void *d, @@ -60,18 +61,19 @@ free(mem); } -static cx_allocator_class cx_default_allocator_class = { +static cx_allocator_class cx_stdlib_allocator_class = { cx_malloc_stdlib, cx_realloc_stdlib, cx_calloc_stdlib, cx_free_stdlib }; -struct cx_allocator_s cx_default_allocator = { - &cx_default_allocator_class, +struct cx_allocator_s cx_stdlib_allocator = { + &cx_stdlib_allocator_class, NULL }; -const CxAllocator * const cxDefaultAllocator = &cx_default_allocator; +const CxAllocator * const cxStdlibAllocator = &cx_stdlib_allocator; +const CxAllocator * cxDefaultAllocator = cxStdlibAllocator; int cx_reallocate_( void **mem, @@ -115,6 +117,17 @@ return allocator->cl->malloc(allocator->data, n); } +void *cxZalloc( + const CxAllocator *allocator, + size_t n +) { + void *mem = allocator->cl->malloc(allocator->data, n); + if (mem != NULL) { + memset(mem, 0, n); + } + return mem; +} + void *cxRealloc( const CxAllocator *allocator, void *mem,
--- a/ucx/array_list.c Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/array_list.c Tue Sep 09 20:56:47 2025 +0200 @@ -36,16 +36,17 @@ static void *cx_array_default_realloc( void *array, - size_t capacity, + cx_attr_unused size_t old_capacity, + size_t new_capacity, size_t elem_size, cx_attr_unused CxArrayReallocator *alloc ) { size_t n; - if (cx_szmul(capacity, elem_size, &n)) { + if (cx_szmul(new_capacity, elem_size, &n)) { errno = EOVERFLOW; return NULL; } - return realloc(array, n); + return cxReallocDefault(array, n); } CxArrayReallocator cx_array_default_reallocator_impl = { @@ -58,13 +59,14 @@ static void *cx_array_advanced_realloc( void *array, - size_t capacity, + size_t old_capacity, + size_t new_capacity, size_t elem_size, cx_attr_unused CxArrayReallocator *alloc ) { // check for overflow size_t n; - if (cx_szmul(capacity, elem_size, &n)) { + if (cx_szmul(new_capacity, elem_size, &n)) { errno = EOVERFLOW; return NULL; } @@ -77,7 +79,7 @@ if (array == alloc->ptr2) { newmem = cxMalloc(al, n); if (newmem != NULL && array != NULL) { - memcpy(newmem, array, n); + memcpy(newmem, array, old_capacity*elem_size); } } else { newmem = cxRealloc(al, array, n); @@ -180,7 +182,7 @@ // perform reallocation void *newmem = reallocator->realloc( - *array, newcap, elem_size, reallocator + *array, oldcap, newcap, elem_size, reallocator ); if (newmem == NULL) { return 1; // LCOV_EXCL_LINE @@ -286,7 +288,7 @@ // perform reallocation void *newmem = reallocator->realloc( - *target, newcap, elem_size, reallocator + *target, oldcap, newcap, elem_size, reallocator ); if (newmem == NULL) { return 1; @@ -366,13 +368,14 @@ // store some counts size_t old_size = *size; + size_t old_capacity = *capacity; size_t needed_capacity = old_size + elem_count; // if we need more than we have, try a reallocation - if (needed_capacity > *capacity) { + if (needed_capacity > old_capacity) { size_t new_capacity = cx_array_align_capacity(needed_capacity, 16, SIZE_MAX); void *new_mem = reallocator->realloc( - *target, new_capacity, elem_size, reallocator + *target, old_capacity, new_capacity, elem_size, reallocator ); if (new_mem == NULL) { // give it up right away, there is no contract @@ -572,7 +575,7 @@ // decide if we can use the local buffer if (elem_size > CX_ARRAY_SWAP_SBO_SIZE) { - tmp = malloc(elem_size); + tmp = cxMallocDefault(elem_size); // we don't want to enforce error handling if (tmp == NULL) abort(); } else { @@ -591,7 +594,7 @@ // free dynamic memory, if it was needed if (tmp != sbo_mem) { - free(tmp); + cxFreeDefault(tmp); } } @@ -638,50 +641,38 @@ // get a correctly typed pointer to the list cx_array_list *arl = (cx_array_list *) list; - // do we need to move some elements? - if (index < list->collection.size) { - const char *first_to_move = (const char *) arl->data; - first_to_move += index * list->collection.elem_size; - size_t elems_to_move = list->collection.size - index; - size_t start_of_moved = index + n; - - if (cx_array_copy( - &arl->data, - &list->collection.size, - &arl->capacity, - 0, - start_of_moved, - first_to_move, - list->collection.elem_size, - elems_to_move, - &arl->reallocator - )) { - // if moving existing elems is unsuccessful, abort + // guarantee enough capacity + if (arl->capacity < list->collection.size + n) { + size_t new_capacity = list->collection.size + n; + new_capacity = new_capacity - (new_capacity % 16) + 16; + if (cxReallocateArray( + list->collection.allocator, + &arl->data, new_capacity, + list->collection.elem_size) + ) { return 0; } + arl->capacity = new_capacity; } - // note that if we had to move the elements, the following operation - // is guaranteed to succeed, because we have the memory already allocated - // therefore, it is impossible to leave this function with an invalid array + // determine insert position + char *arl_data = arl->data; + char *insert_pos = arl_data + index * list->collection.elem_size; - // place the new elements - if (cx_array_copy( - &arl->data, - &list->collection.size, - &arl->capacity, - 0, - index, - array, - list->collection.elem_size, - n, - &arl->reallocator - )) { - // array list implementation is "all or nothing" - return 0; - } else { - return n; + // do we need to move some elements? + if (index < list->collection.size) { + size_t elems_to_move = list->collection.size - index; + char *target = insert_pos + n * list->collection.elem_size; + memmove(target, insert_pos, elems_to_move * list->collection.elem_size); } + + // place the new elements, if any + if (array != NULL) { + memcpy(insert_pos, array, n * list->collection.elem_size); + } + list->collection.size += n; + + return n; } static size_t cx_arl_insert_sorted( @@ -709,12 +700,16 @@ } } -static int cx_arl_insert_element( +static void *cx_arl_insert_element( struct cx_list_s *list, size_t index, const void *element ) { - return 1 != cx_arl_insert_array(list, index, element, 1); + if (cx_arl_insert_array(list, index, element, 1) == 1) { + return ((char*)((cx_array_list *) list)->data) + index * list->collection.elem_size; + } else { + return NULL; + } } static int cx_arl_insert_iter( @@ -724,26 +719,23 @@ ) { struct cx_list_s *list = iter->src_handle.m; if (iter->index < list->collection.size) { - int result = cx_arl_insert_element( - list, - iter->index + 1 - prepend, - elem - ); - if (result == 0) { - iter->elem_count++; - if (prepend != 0) { - iter->index++; - iter->elem_handle = ((char *) iter->elem_handle) + list->collection.elem_size; - } + if (cx_arl_insert_element(list, + iter->index + 1 - prepend, elem) == NULL) { + return 1; + } + iter->elem_count++; + if (prepend != 0) { + iter->index++; + iter->elem_handle = ((char *) iter->elem_handle) + list->collection.elem_size; } - return result; + return 0; } else { - int result = cx_arl_insert_element(list, list->collection.size, elem); - if (result == 0) { - iter->elem_count++; - iter->index = list->collection.size; + if (cx_arl_insert_element(list, list->collection.size, elem) == NULL) { + return 1; } - return result; + iter->elem_count++; + iter->index = list->collection.size; + return 0; } }
--- a/ucx/buffer.c Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/buffer.c Tue Sep 09 20:56:47 2025 +0200 @@ -32,6 +32,24 @@ #include <string.h> #include <errno.h> +#ifdef _WIN32 +#include <Windows.h> +#include <sysinfoapi.h> +static unsigned long system_page_size() { + static unsigned long ps = 0; + if (ps == 0) { + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + ps = sysinfo.dwPageSize; + } + return ps; +} +#define SYSTEM_PAGE_SIZE system_page_size() +#else +#include <unistd.h> +#define SYSTEM_PAGE_SIZE sysconf(_SC_PAGESIZE) +#endif + static int buffer_copy_on_write(CxBuffer* buffer) { if (0 == (buffer->flags & CX_BUFFER_COPY_ON_WRITE)) return 0; void *newspace = cxMalloc(buffer->allocator, buffer->capacity); @@ -80,7 +98,7 @@ CxBuffer *buffer, CxBufferFlushConfig config ) { - buffer->flush = malloc(sizeof(CxBufferFlushConfig)); + buffer->flush = cxMallocDefault(sizeof(CxBufferFlushConfig)); if (buffer->flush == NULL) return -1; // LCOV_EXCL_LINE memcpy(buffer->flush, &config, sizeof(CxBufferFlushConfig)); return 0; @@ -90,7 +108,7 @@ if (buffer->flags & CX_BUFFER_FREE_CONTENTS) { cxFree(buffer->allocator, buffer->bytes); } - free(buffer->flush); + cxFreeDefault(buffer->flush); memset(buffer, 0, sizeof(CxBuffer)); } @@ -139,6 +157,7 @@ npos = 0; break; default: + errno = EINVAL; return -1; } @@ -146,11 +165,16 @@ npos += offset; if ((offset > 0 && npos < opos) || (offset < 0 && npos > opos)) { - errno = EOVERFLOW; + // to be compliant with fseek() specification + // we return EINVAL on underflow + errno = EINVAL; return -1; } if (npos > buffer->size) { + // not compliant with fseek() specification + // but this is the better behavior for CxBuffer + errno = EINVAL; return -1; } else { buffer->pos = npos; @@ -184,6 +208,28 @@ return 0; } + unsigned long pagesize = SYSTEM_PAGE_SIZE; + // if page size is larger than 64 KB - for some reason - truncate to 64 KB + if (pagesize > 65536) pagesize = 65536; + if (newcap < pagesize) { + // when smaller as one page, map to the next power of two + newcap--; + newcap |= newcap >> 1; + newcap |= newcap >> 2; + newcap |= newcap >> 4; + // last operation only needed for pages larger 4096 bytes + // but if/else would be more expensive than just doing this + newcap |= newcap >> 8; + newcap++; + } else { + // otherwise, map to a multiple of the page size + newcap -= newcap % pagesize; + newcap += pagesize; + // note: if newcap is already page aligned, + // this gives a full additional page (which is good) + } + + const int force_copy_flags = CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_COPY_ON_EXTEND; if (buffer->flags & force_copy_flags) { void *newspace = cxMalloc(buffer->allocator, newcap); @@ -203,6 +249,28 @@ } } +void cxBufferShrink( + CxBuffer *buffer, + size_t reserve +) { + // Ensure buffer is in a reallocatable state + const int force_copy_flags = CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_COPY_ON_EXTEND; + if (buffer->flags & force_copy_flags) { + // do nothing when we are not allowed to reallocate + return; + } + + // calculate new capacity + size_t newCapacity = buffer->size + reserve; + + // If new capacity is smaller than current capacity, resize the buffer + if (newCapacity < buffer->capacity) { + if (0 == cxReallocate(buffer->allocator, &buffer->bytes, newCapacity)) { + buffer->capacity = newCapacity; + } + } +} + static size_t cx_buffer_flush_helper( const CxBuffer *buffer, const unsigned char *src, @@ -399,10 +467,8 @@ } int cxBufferTerminate(CxBuffer *buffer) { - bool success = 0 == cxBufferPut(buffer, 0); - if (success) { - buffer->pos--; - buffer->size--; + if (0 == cxBufferPut(buffer, 0)) { + buffer->size = buffer->pos - 1; return 0; } else { return -1;
--- a/ucx/cx/allocator.h Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/cx/allocator.h Tue Sep 09 20:56:47 2025 +0200 @@ -98,10 +98,17 @@ typedef struct cx_allocator_s CxAllocator; /** - * A default allocator using standard library malloc() etc. + * A pre-defined allocator using standard library malloc() etc. */ cx_attr_export -extern const CxAllocator * const cxDefaultAllocator; +extern const CxAllocator * const cxStdlibAllocator; + +/** + * The default allocator that is used by UCX. + * Initialized with cxStdlibAllocator, but you may change it. + */ +cx_attr_export +extern const CxAllocator * cxDefaultAllocator; /** * Function pointer type for destructor functions. @@ -135,6 +142,8 @@ * Reallocate a previously allocated block and changes the pointer in-place, * if necessary. * + * @note This will use stdlib reallocate and @em not the cxDefaultAllocator. + * * @par Error handling * @c errno will be set by realloc() on failure. * @@ -158,6 +167,8 @@ * * The size is calculated by multiplying @p nemb and @p size. * + * @note This will use stdlib reallocate and @em not the cxDefaultAllocator. + * * @par Error handling * @c errno will be set by realloc() on failure or when the multiplication of * @p nmemb and @p size overflows. @@ -182,6 +193,8 @@ * Reallocate a previously allocated block and changes the pointer in-place, * if necessary. * + * @note This will use stdlib reallocate and @em not the cxDefaultAllocator. + * * @par Error handling * @c errno will be set by realloc() on failure. * @@ -199,6 +212,8 @@ * * The size is calculated by multiplying @p nemb and @p size. * + * @note This will use stdlib reallocate and @em not the cxDefaultAllocator. + * * @par Error handling * @c errno will be set by realloc() on failure or when the multiplication of * @p nmemb and @p size overflows. @@ -213,6 +228,14 @@ cx_reallocatearray_((void**)(mem), nmemb, size) /** + * Allocates memory and sets every byte to zero. + * + * @param n (@c size_t) the number of bytes + * @return (@c void*) a pointer to the allocated memory + */ +#define cx_zalloc(n) calloc(1, n) + +/** * Free a block allocated by this allocator. * * @note Freeing a block of a different allocator is undefined. @@ -414,6 +437,57 @@ size_t size ); +/** + * Allocate @p n bytes of memory and sets every byte to zero. + * + * @param allocator the allocator + * @param n the number of bytes + * @return a pointer to the allocated memory + */ +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_malloc +cx_attr_dealloc_ucx +cx_attr_allocsize(2) +cx_attr_export +void *cxZalloc( + const CxAllocator *allocator, + size_t n +); + +/** + * Convenience macro that invokes cxMalloc() with the cxDefaultAllocator. + */ +#define cxMallocDefault(...) cxMalloc(cxDefaultAllocator, __VA_ARGS__) +/** + * Convenience macro that invokes cxZalloc() with the cxDefaultAllocator. + */ +#define cxZallocDefault(...) cxZalloc(cxDefaultAllocator, __VA_ARGS__) +/** + * Convenience macro that invokes cxCalloc() with the cxDefaultAllocator. + */ +#define cxCallocDefault(...) cxCalloc(cxDefaultAllocator, __VA_ARGS__) +/** + * Convenience macro that invokes cxRealloc() with the cxDefaultAllocator. + */ +#define cxReallocDefault(...) cxRealloc(cxDefaultAllocator, __VA_ARGS__) +/** + * Convenience macro that invokes cxReallocate() with the cxDefaultAllocator. + */ +#define cxReallocateDefault(...) cxReallocate(cxDefaultAllocator, __VA_ARGS__) +/** + * Convenience macro that invokes cxReallocateArray() with the cxDefaultAllocator. + */ +#define cxReallocateArrayDefault(...) cxReallocateArray(cxDefaultAllocator, __VA_ARGS__) +/** + * Convenience macro that invokes cxReallocArray() with the cxDefaultAllocator. + */ +#define cxReallocArrayDefault(...) cxReallocArray(cxDefaultAllocator, __VA_ARGS__) +/** + * Convenience macro that invokes cxFree() with the cxDefaultAllocator. + */ +#define cxFreeDefault(...) cxFree(cxDefaultAllocator, __VA_ARGS__) + #ifdef __cplusplus } // extern "C" #endif
--- a/ucx/cx/array_list.h Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/cx/array_list.h Tue Sep 09 20:56:47 2025 +0200 @@ -123,7 +123,8 @@ * @endcode * * - * The memory for the array is allocated with stdlib malloc(). + * The memory for the array is allocated with the cxDefaultAllocator. + * * @param array the name of the array * @param capacity the initial capacity * @see cx_array_initialize_a() @@ -133,7 +134,7 @@ #define cx_array_initialize(array, capacity) \ array##_capacity = capacity; \ array##_size = 0; \ - array = malloc(sizeof(array[0]) * capacity) + array = cxMallocDefault(sizeof(array[0]) * capacity) /** * Initializes an array with the given capacity using the specified allocator. @@ -149,7 +150,6 @@ * cxFree(al, myarray); // don't forget to free with same allocator * @endcode * - * The memory for the array is allocated with stdlib malloc(). * @param allocator (@c CxAllocator*) the allocator * @param array the name of the array * @param capacity the initial capacity @@ -178,17 +178,19 @@ * or to transport other additional data. * * @param array the array to reallocate - * @param capacity the new capacity (number of elements) + * @param old_capacity the old number of elements + * @param new_capacity the new number of elements * @param elem_size the size of each element * @param alloc a reference to this allocator * @return a pointer to the reallocated memory or @c NULL on failure */ cx_attr_nodiscard - cx_attr_nonnull_arg(4) - cx_attr_allocsize(2, 3) + cx_attr_nonnull_arg(5) + cx_attr_allocsize(3, 4) void *(*realloc)( void *array, - size_t capacity, + size_t old_capacity, + size_t new_capacity, size_t elem_size, struct cx_array_reallocator_s *alloc ); @@ -217,7 +219,7 @@ typedef struct cx_array_reallocator_s CxArrayReallocator; /** - * A default stdlib-based array reallocator. + * A default array reallocator that is based on the cxDefaultAllocator. */ cx_attr_export extern CxArrayReallocator *cx_array_default_reallocator; @@ -225,7 +227,7 @@ /** * Creates a new array reallocator. * - * When @p allocator is @c NULL, the stdlib default allocator will be used. + * When @p allocator is @c NULL, the cxDefaultAllocator will be used. * * When @p stackmem is not @c NULL, the reallocator is supposed to be used * @em only for the specific array that is initially located at @p stackmem. @@ -699,7 +701,7 @@ * to cx_cmp_ptr(), if none is given. * * @param allocator the allocator for allocating the list memory - * (if @c NULL, a default stdlib allocator will be used) + * (if @c NULL, the cxDefaultAllocator will be used) * @param comparator the comparator for the elements * (if @c NULL, and the list is not storing pointers, sort and find * functions will not work) @@ -727,7 +729,7 @@ * * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of * copies of the added elements and the compare function will be automatically set - * to cx_cmp_ptr(), if none is given. + * to cx_cmp_ptr(). * * @param elem_size (@c size_t) the size of each element in bytes * @param initial_capacity (@c size_t) the initial number of elements the array can store
--- a/ucx/cx/buffer.h Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/cx/buffer.h Tue Sep 09 20:56:47 2025 +0200 @@ -222,7 +222,7 @@ * @param capacity the capacity of the buffer * @param allocator the allocator this buffer shall use for automatic * memory management - * (if @c NULL, a default stdlib allocator will be used) + * (if @c NULL, the cxDefaultAllocator will be used) * @param flags buffer features (see cx_buffer_s.flags) * @return zero on success, non-zero if a required allocation failed */ @@ -305,7 +305,7 @@ * @param capacity the capacity of the buffer * @param allocator the allocator to use for allocating the structure and the automatic * memory management within the buffer - * (if @c NULL, a default stdlib allocator will be used) + * (if @c NULL, the cxDefaultAllocator will be used) * @param flags buffer features (see cx_buffer_s.flags) * @return a pointer to the buffer on success, @c NULL if a required allocation failed */ @@ -474,10 +474,14 @@ * * If the current capacity is not sufficient, the buffer will be extended. * + * The new capacity will be a power of two until the system's page size is reached. + * Then, the new capacity will be a multiple of the page size. + * * @param buffer the buffer * @param capacity the minimum required capacity for this buffer * @retval zero the capacity was already sufficient or successfully increased * @retval non-zero on allocation failure + * @see cxBufferShrink() */ cx_attr_nonnull cx_attr_export @@ -487,6 +491,29 @@ ); /** + * Shrinks the capacity of the buffer to fit its current size. + * + * If @p reserve is larger than zero, the buffer is shrunk to its size plus + * the number of reserved bytes. + * + * If the current capacity is not larger than the size plus the reserved bytes, + * nothing happens. + * + * If the #CX_BUFFER_COPY_ON_WRITE or #CX_BUFFER_COPY_ON_EXTEND flag is set, + * this function does nothing. + * + * @param buffer the buffer + * @param reserve the number of bytes that shall remain reserved + * @see cxBufferMinimumCapacity() + */ +cx_attr_nonnull +cx_attr_export +void cxBufferShrink( + CxBuffer *buffer, + size_t reserve +); + +/** * Writes data to a CxBuffer. * * If automatic flushing is not enabled, the data is simply written into the @@ -674,11 +701,10 @@ /** * Writes a terminating zero to a buffer at the current position. * - * On successful write, @em neither the position @em nor the size of the buffer is - * increased. + * If successful, sets the size to the current position and advances the position by one. * * The purpose of this function is to have the written data ready to be used as - * a C string. + * a C string with the buffer's size being the length of that string. * * @param buffer the buffer to write to * @return zero, if the terminator could be written, non-zero otherwise
--- a/ucx/cx/common.h Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/cx/common.h Tue Sep 09 20:56:47 2025 +0200 @@ -46,7 +46,7 @@ * Repositories:<br> * <a href="https://sourceforge.net/p/ucx/code">https://sourceforge.net/p/ucx/code</a> * - or - - * <a href="https://develop.uap-core.de/hg/ucx">https://develop.uap-core.de/hg/ucx</a> + * <a href="https://uap-core.de/hg/ucx">https://uap-core.de/hg/ucx</a> * </p> * * <h2>LICENCE</h2> @@ -131,6 +131,11 @@ #endif /** + * Inform the compiler that falling through a switch case is intentional. + */ +#define cx_attr_fallthrough __attribute__((__fallthrough__)) + +/** * All pointer arguments must be non-NULL. */ #define cx_attr_nonnull __attribute__((__nonnull__)) @@ -150,7 +155,7 @@ */ #define cx_attr_malloc __attribute__((__malloc__)) -#ifndef __clang__ +#if !defined(__clang__) && __GNUC__ >= 11 /** * The pointer returned by the attributed function is supposed to be freed * by @p freefunc. @@ -240,20 +245,6 @@ */ #define cx_attr_access_w(...) cx_attr_access(__write_only__, __VA_ARGS__) -#if __STDC_VERSION__ >= 202300L - -/** - * Do not warn about unused variable. - */ -#define cx_attr_unused [[maybe_unused]] - -/** - * Warn about discarded return value. - */ -#define cx_attr_nodiscard [[nodiscard]] - -#else // no C23 - /** * Do not warn about unused variable. */ @@ -264,8 +255,6 @@ */ #define cx_attr_nodiscard __attribute__((__warn_unused_result__)) -#endif // __STDC_VERSION__ - // --------------------------------------------------------------------------- // MSVC specifics
--- a/ucx/cx/hash_map.h Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/cx/hash_map.h Tue Sep 09 20:56:47 2025 +0200 @@ -77,7 +77,7 @@ * In other words, when the iterator is finished, @c index==size . * * @param allocator the allocator to use - * (if @c NULL, a default stdlib allocator will be used) + * (if @c NULL, the cxDefaultAllocator will be used) * @param itemsize the size of one element * @param buckets the initial number of buckets in this hash map * @return a pointer to the new hash map
--- a/ucx/cx/json.h Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/cx/json.h Tue Sep 09 20:56:47 2025 +0200 @@ -1284,6 +1284,23 @@ CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index); /** + * Removes an element from a JSON array. + * + * If the @p value is not a JSON array, the behavior is undefined. + * + * This function, in contrast to cxJsonArrayGet(), returns @c NULL + * when the index is out of bounds. + * + * @param value the JSON value + * @param index the index in the array + * @return the removed value from the specified index or @c NULL when the index was out of bounds + * @see cxJsonIsArray() + */ +cx_attr_nonnull +cx_attr_export +CxJsonValue *cxJsonArrRemove(CxJsonValue *value, size_t index); + +/** * Returns an iterator over the JSON array elements. * * The iterator yields values of type @c CxJsonValue* . @@ -1324,6 +1341,13 @@ cx_attr_export CxJsonValue *cx_json_obj_get_cxstr(const CxJsonValue *value, cxstring name); +/** + * @copydoc cxJsonObjRemove() + */ +cx_attr_nonnull +cx_attr_export +CxJsonValue *cx_json_obj_remove_cxstr(CxJsonValue *value, cxstring name); + #ifdef __cplusplus } // extern "C" @@ -1339,6 +1363,18 @@ return cx_json_obj_get_cxstr(value, cx_str(name)); } +static inline CxJsonValue *cxJsonObjRemove(CxJsonValue *value, cxstring name) { + return cx_json_obj_remove_cxstr(value, name); +} + +static inline CxJsonValue *cxJsonObjRemove(CxJsonValue *value, cxmutstr name) { + return cx_json_obj_remove_cxstr(value, cx_strcast(name)); +} + +static inline CxJsonValue *cxJsonObjRemove(CxJsonValue *value, const char *name) { + return cx_json_obj_remove_cxstr(value, cx_str(name)); +} + extern "C" { #else /** @@ -1380,6 +1416,43 @@ static inline CxJsonValue *cx_json_obj_get_str(const CxJsonValue *value, const char *name) { return cx_json_obj_get_cxstr(value, cx_str(name)); } + +/** + * Removes and returns a value corresponding to a key in a JSON object. + * + * If the @p value is not a JSON object, the behavior is undefined. + * + * This function, in contrast to cxJsonObjGet() returns @c NULL when the + * object does not contain @p name. + * + * @param value the JSON object + * @param name the key to look up + * @return the value corresponding to the key or @c NULL when the key is not part of the object + * @see cxJsonIsObject() + */ +#define cxJsonObjRemove(value, name) _Generic((name), \ + cxstring: cx_json_obj_remove_cxstr, \ + cxmutstr: cx_json_obj_remove_mutstr, \ + char*: cx_json_obj_remove_str, \ + const char*: cx_json_obj_remove_str) \ + (value, name) + +/** + * @copydoc cxJsonObjRemove() + */ +cx_attr_nonnull +static inline CxJsonValue *cx_json_obj_remove_mutstr(CxJsonValue *value, cxmutstr name) { + return cx_json_obj_remove_cxstr(value, cx_strcast(name)); +} + +/** + * @copydoc cxJsonObjRemove() + */ +cx_attr_nonnull +cx_attr_cstr_arg(2) +static inline CxJsonValue *cx_json_obj_remove_str(CxJsonValue *value, const char *name) { + return cx_json_obj_remove_cxstr(value, cx_str(name)); +} #endif #ifdef __cplusplus
--- a/ucx/cx/linked_list.h Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/cx/linked_list.h Tue Sep 09 20:56:47 2025 +0200 @@ -51,7 +51,7 @@ * to cx_cmp_ptr(), if none is given. * * @param allocator the allocator for allocating the list nodes - * (if @c NULL, a default stdlib allocator will be used) + * (if @c NULL, the cxDefaultAllocator will be used) * @param comparator the comparator for the elements * (if @c NULL, and the list is not storing pointers, sort and find * functions will not work) @@ -77,7 +77,7 @@ * * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of * copies of the added elements and the compare function will be automatically set - * to cx_cmp_ptr(), if none is given. + * to cx_cmp_ptr(). * * @param elem_size (@c size_t) the size of each element in bytes * @return (@c CxList*) the created list @@ -393,7 +393,7 @@ * @li @p loc_next and @p loc_prev (ancestor node is determined by using the prev pointer, overall O(1) performance) * @li @p loc_next and @p begin (ancestor node is determined by list traversal, overall O(n) performance) * - * @remark The @c next and @c prev pointers of the removed node are not cleared by this function and may still be used + * @remark The @c next and @c prev pointers of the removed chain are not cleared by this function and may still be used * to traverse to a former adjacent node in the list, or within the chain. * * @param begin a pointer to the beginning node pointer (optional)
--- a/ucx/cx/list.h Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/cx/list.h Tue Sep 09 20:56:47 2025 +0200 @@ -80,8 +80,10 @@ /** * Member function for inserting a single element. + * The data pointer may be @c NULL in which case the function shall only allocate memory. + * Returns a pointer to the allocated memory or @c NULL if allocation fails. */ - int (*insert_element)( + void *(*insert_element)( struct cx_list_s *list, size_t index, const void *data @@ -181,7 +183,6 @@ * to another list of the same type. * If set to @c NULL, comparison won't be optimized. */ - cx_attr_nonnull int (*compare)( const struct cx_list_s *list, const struct cx_list_s *other @@ -203,6 +204,22 @@ }; /** + * Common type for all list implementations. + */ +typedef struct cx_list_s CxList; + +/** + * A shared instance of an empty list. + * + * Writing to that list is not allowed. + * + * You can use this is a placeholder for initializing CxList pointers + * for which you do not want to reserve memory right from the beginning. + */ +cx_attr_export +extern CxList *const cxEmptyList; + +/** * Default implementation of an array insert. * * This function uses the element insert function for each element of the array. @@ -336,11 +353,6 @@ ); /** - * Common type for all list implementations. - */ -typedef struct cx_list_s CxList; - -/** * Returns the number of elements currently stored in the list. * * @param list the list @@ -359,6 +371,7 @@ * @retval zero success * @retval non-zero memory allocation failure * @see cxListAddArray() + * @see cxListEmplace() */ cx_attr_nonnull static inline int cxListAdd( @@ -366,7 +379,7 @@ const void *elem ) { list->collection.sorted = false; - return list->cl->insert_element(list, list->collection.size, elem); + return list->cl->insert_element(list, list->collection.size, elem) == NULL; } /** @@ -407,6 +420,7 @@ * @retval non-zero memory allocation failure or the index is out of bounds * @see cxListInsertAfter() * @see cxListInsertBefore() + * @see cxListEmplaceAt() */ cx_attr_nonnull static inline int cxListInsert( @@ -415,7 +429,41 @@ const void *elem ) { list->collection.sorted = false; - return list->cl->insert_element(list, index, elem); + return list->cl->insert_element(list, index, elem) == NULL; +} + +/** + * Allocates memory for an element at the specified index and returns a pointer to that memory. + * + * @remark When the list is storing pointers, this will return a @c void**. + * + * @param list the list + * @param index the index where to emplace the element + * @return a pointer to the allocated memory; @c NULL when the operation fails, or the index is out-of-bounds + * @see cxListEmplace() + * @see cxListInsert() + */ +cx_attr_nonnull +static inline void *cxListEmplaceAt(CxList *list, size_t index) { + list->collection.sorted = false; + return list->cl->insert_element(list, index, NULL); +} + + +/** + * Allocates memory for an element at the end of the list and returns a pointer to that memory. + * + * @remark When the list is storing pointers, this will return a @c void**. + * + * @param list the list + * @return a pointer to the allocated memory; @c NULL when the operation fails, or the index is out-of-bounds + * @see cxListEmplaceAt() + * @see cxListAdd() + */ +cx_attr_nonnull +static inline void *cxListEmplace(CxList *list) { + list->collection.sorted = false; + return list->cl->insert_element(list, list->collection.size, NULL); } /** @@ -571,8 +619,9 @@ /** * Removes and returns the element at the specified index. * - * No destructor is called and instead the element is copied to the + * No destructor is called, and instead the element is copied to the * @p targetbuf which MUST be large enough to hold the removed element. + * If the list is storing pointers, only the pointer is copied to @p targetbuf. * * @param list the list * @param index the index of the element @@ -591,11 +640,93 @@ } /** + * Removes and returns the first element of the list. + * + * No destructor is called, and instead the element is copied to the + * @p targetbuf which MUST be large enough to hold the removed element. + * If the list is storing pointers, only the pointer is copied to @p targetbuf. + * + * @param list the list + * @param targetbuf a buffer where to copy the element + * @retval zero success + * @retval non-zero list is empty + * @see cxListPopFront() + * @see cxListRemoveAndGetLast() + */ +cx_attr_nonnull +cx_attr_access_w(2) +static inline int cxListRemoveAndGetFirst( + CxList *list, + void *targetbuf +) { + return list->cl->remove(list, 0, 1, targetbuf) == 0; +} + +/** + * Removes and returns the first element of the list. + * + * Alias for cxListRemoveAndGetFirst(). + * + * No destructor is called, and instead the element is copied to the + * @p targetbuf which MUST be large enough to hold the removed element. + * If the list is storing pointers, only the pointer is copied to @p targetbuf. + * + * @param list (@c CxList*) the list + * @param targetbuf (@c void*) a buffer where to copy the element + * @retval zero success + * @retval non-zero list is empty + * @see cxListRemoveAndGetFirst() + * @see cxListPop() + */ +#define cxListPopFront(list, targetbuf) cxListRemoveAndGetFirst((list), (targetbuf)) + + +/** + * Removes and returns the last element of the list. + * + * No destructor is called, and instead the element is copied to the + * @p targetbuf which MUST be large enough to hold the removed element. + * If the list is storing pointers, only the pointer is copied to @p targetbuf. + * + * @param list the list + * @param targetbuf a buffer where to copy the element + * @retval zero success + * @retval non-zero list is empty + */ +cx_attr_nonnull +cx_attr_access_w(2) +static inline int cxListRemoveAndGetLast( + CxList *list, + void *targetbuf +) { + // note: index may wrap - member function will catch that + return list->cl->remove(list, list->collection.size - 1, 1, targetbuf) == 0; +} + +/** + * Removes and returns the last element of the list. + * + * Alias for cxListRemoveAndGetLast(). + * + * No destructor is called, and instead the element is copied to the + * @p targetbuf which MUST be large enough to hold the removed element. + * If the list is storing pointers, only the pointer is copied to @p targetbuf. + * + * @param list (@c CxList*) the list + * @param targetbuf (@c void*) a buffer where to copy the element + * @retval zero success + * @retval non-zero list is empty + * @see cxListRemoveAndGetLast() + * @see cxListPopFront() + */ +#define cxListPop(list, targetbuf) cxListRemoveAndGetLast((list), (targetbuf)) + +/** * Removes multiple element starting at the specified index. * * If an element destructor function is specified, it is called for each * element. It is guaranteed that the destructor is called before removing - * the element, however, due to possible optimizations it is neither guaranteed + * the element. However, due to possible optimizations, it is neither guaranteed * that the destructors are invoked for all elements before starting to remove * them, nor that the element is removed immediately after the destructor call * before proceeding to the next element. @@ -615,10 +746,11 @@ } /** - * Removes and returns multiple element starting at the specified index. + * Removes and returns multiple elements starting at the specified index. * - * No destructor is called and instead the elements are copied to the + * No destructor is called, and instead the elements are copied to the * @p targetbuf which MUST be large enough to hold all removed elements. + * If the list is storing pointers, @p targetbuf is expected to be an array of pointers. * * @param list the list * @param index the index of the element @@ -654,15 +786,15 @@ /** * Swaps two items in the list. * - * Implementations should only allocate temporary memory for the swap, if + * Implementations should only allocate temporary memory for the swap if * it is necessary. * * @param list the list * @param i the index of the first element * @param j the index of the second element * @retval zero success - * @retval non-zero one of the indices is out of bounds - * or the swap needed extra memory but allocation failed + * @retval non-zero one of the indices is out of bounds, + * or the swap needed extra memory, but allocation failed */ cx_attr_nonnull static inline int cxListSwap( @@ -677,6 +809,8 @@ /** * Returns a pointer to the element at the specified index. * + * If the list is storing pointers, returns the pointer stored at the specified index. + * * @param list the list * @param index the index of the element * @return a pointer to the element or @c NULL if the index is out of bounds @@ -690,22 +824,65 @@ } /** + * Returns a pointer to the first element. + * + * If the list is storing pointers, returns the first pointer stored in the list. + * + * @param list the list + * @return a pointer to the first element or @c NULL if the list is empty + */ +cx_attr_nonnull +static inline void *cxListFirst(const CxList *list) { + return list->cl->at(list, 0); +} + +/** + * Returns a pointer to the last element. + * + * If the list is storing pointers, returns the last pointer stored in the list. + * + * @param list the list + * @return a pointer to the last element or @c NULL if the list is empty + */ +cx_attr_nonnull +static inline void *cxListLast(const CxList *list) { + return list->cl->at(list, list->collection.size - 1); +} + +/** + * Sets the element at the specified index in the list + * + * @param list the list to set the element in + * @param index the index to set the element at + * @param elem element to set + * @retval zero on success + * @retval non-zero when index is out of bounds + */ +cx_attr_nonnull +cx_attr_export +int cxListSet( + CxList *list, + size_t index, + const void *elem +); + +/** * Returns an iterator pointing to the item at the specified index. * * The returned iterator is position-aware. * - * If the index is out of range, a past-the-end iterator will be returned. + * If the index is out of range or @p list is @c NULL, a past-the-end iterator will be returned. * * @param list the list * @param index the index where the iterator shall point at * @return a new iterator */ -cx_attr_nonnull cx_attr_nodiscard static inline CxIterator cxListIteratorAt( const CxList *list, size_t index ) { + if (list == NULL) list = cxEmptyList; return list->cl->iterator(list, index, false); } @@ -714,18 +891,18 @@ * * The returned iterator is position-aware. * - * If the index is out of range, a past-the-end iterator will be returned. + * If the index is out of range or @p list is @c NULL, a past-the-end iterator will be returned. * * @param list the list * @param index the index where the iterator shall point at * @return a new iterator */ -cx_attr_nonnull cx_attr_nodiscard static inline CxIterator cxListBackwardsIteratorAt( const CxList *list, size_t index ) { + if (list == NULL) list = cxEmptyList; return list->cl->iterator(list, index, true); } @@ -734,13 +911,12 @@ * * The returned iterator is position-aware. * - * If the index is out of range, a past-the-end iterator will be returned. + * If the index is out of range or @p list is @c NULL, a past-the-end iterator will be returned. * * @param list the list * @param index the index where the iterator shall point at * @return a new iterator */ -cx_attr_nonnull cx_attr_nodiscard cx_attr_export CxIterator cxListMutIteratorAt( @@ -754,13 +930,12 @@ * * The returned iterator is position-aware. * - * If the index is out of range, a past-the-end iterator will be returned. + * If the index is out of range or @p list is @c NULL, a past-the-end iterator will be returned. * * @param list the list * @param index the index where the iterator shall point at * @return a new iterator */ -cx_attr_nonnull cx_attr_nodiscard cx_attr_export CxIterator cxListMutBackwardsIteratorAt( @@ -773,14 +948,14 @@ * * The returned iterator is position-aware. * - * If the list is empty, a past-the-end iterator will be returned. + * If the list is empty or @c NULL, a past-the-end iterator will be returned. * * @param list the list * @return a new iterator */ -cx_attr_nonnull cx_attr_nodiscard static inline CxIterator cxListIterator(const CxList *list) { + if (list == NULL) list = cxEmptyList; return list->cl->iterator(list, 0, false); } @@ -789,14 +964,14 @@ * * The returned iterator is position-aware. * - * If the list is empty, a past-the-end iterator will be returned. + * If the list is empty or @c NULL, a past-the-end iterator will be returned. * * @param list the list * @return a new iterator */ -cx_attr_nonnull cx_attr_nodiscard static inline CxIterator cxListMutIterator(CxList *list) { + if (list == NULL) list = cxEmptyList; return cxListMutIteratorAt(list, 0); } @@ -806,14 +981,14 @@ * * The returned iterator is position-aware. * - * If the list is empty, a past-the-end iterator will be returned. + * If the list is empty or @c NULL, a past-the-end iterator will be returned. * * @param list the list * @return a new iterator */ -cx_attr_nonnull cx_attr_nodiscard static inline CxIterator cxListBackwardsIterator(const CxList *list) { + if (list == NULL) list = cxEmptyList; return list->cl->iterator(list, list->collection.size - 1, true); } @@ -822,14 +997,14 @@ * * The returned iterator is position-aware. * - * If the list is empty, a past-the-end iterator will be returned. + * If the list is empty or @c NULL, a past-the-end iterator will be returned. * * @param list the list * @return a new iterator */ -cx_attr_nonnull cx_attr_nodiscard static inline CxIterator cxListMutBackwardsIterator(CxList *list) { + if (list == NULL) list = cxEmptyList; return cxListMutBackwardsIteratorAt(list, list->collection.size - 1); } @@ -842,6 +1017,7 @@ * @param elem the element to find * @return the index of the element or the size of the list when the element is not found * @see cxListIndexValid() + * @see cxListContains() */ cx_attr_nonnull cx_attr_nodiscard @@ -853,6 +1029,26 @@ } /** + * Checks, if the list contains the specified element. + * + * The elements are compared with the list's comparator function. + * + * @param list the list + * @param elem the element to find + * @retval true if the element is contained + * @retval false if the element is not contained + * @see cxListFind() + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline bool cxListContains( + const CxList* list, + const void* elem +) { + return list->cl->find_remove((CxList*)list, elem, false) < list->collection.size; +} + +/** * Checks if the specified index is within bounds. * * @param list the list @@ -894,6 +1090,7 @@ */ cx_attr_nonnull static inline void cxListSort(CxList *list) { + if (list->collection.sorted) return; list->cl->sort(list); list->collection.sorted = true; } @@ -942,17 +1139,6 @@ cx_attr_export void cxListFree(CxList *list); -/** - * A shared instance of an empty list. - * - * Writing to that list is not allowed. - * - * You can use this is a placeholder for initializing CxList pointers - * for which you do not want to reserve memory right from the beginning. - */ -cx_attr_export -extern CxList *const cxEmptyList; - #ifdef __cplusplus } // extern "C"
--- a/ucx/cx/map.h Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/cx/map.h Tue Sep 09 20:56:47 2025 +0200 @@ -185,8 +185,11 @@ /** * Add or overwrite an element. + * If the @p value is @c NULL, the implementation + * shall only allocate memory instead of adding an existing value to the map. + * Returns a pointer to the allocated memory or @c NULL if allocation fails. */ - int (*put)( + void *(*put)( CxMap *map, CxHashKey key, void *value @@ -277,12 +280,12 @@ * @note An iterator iterates over all elements successively. Therefore, the order * highly depends on the map implementation and may change arbitrarily when the contents change. * - * @param map the map to create the iterator for + * @param map the map to create the iterator for (can be @c NULL) * @return an iterator for the currently stored values */ -cx_attr_nonnull cx_attr_nodiscard static inline CxMapIterator cxMapIteratorValues(const CxMap *map) { + if (map == NULL) map = cxEmptyMap; return map->cl->iterator(map, CX_MAP_ITERATOR_VALUES); } @@ -295,12 +298,12 @@ * @note An iterator iterates over all elements successively. Therefore, the order * highly depends on the map implementation and may change arbitrarily when the contents change. * - * @param map the map to create the iterator for + * @param map the map to create the iterator for (can be @c NULL) * @return an iterator for the currently stored keys */ -cx_attr_nonnull cx_attr_nodiscard static inline CxMapIterator cxMapIteratorKeys(const CxMap *map) { + if (map == NULL) map = cxEmptyMap; return map->cl->iterator(map, CX_MAP_ITERATOR_KEYS); } @@ -313,14 +316,14 @@ * @note An iterator iterates over all elements successively. Therefore, the order * highly depends on the map implementation and may change arbitrarily when the contents change. * - * @param map the map to create the iterator for + * @param map the map to create the iterator for (can be @c NULL) * @return an iterator for the currently stored entries * @see cxMapIteratorKeys() * @see cxMapIteratorValues() */ -cx_attr_nonnull cx_attr_nodiscard static inline CxMapIterator cxMapIterator(const CxMap *map) { + if (map == NULL) map = cxEmptyMap; return map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS); } @@ -335,10 +338,9 @@ * @note An iterator iterates over all elements successively. Therefore, the order * highly depends on the map implementation and may change arbitrarily when the contents change. * - * @param map the map to create the iterator for + * @param map the map to create the iterator for (can be @c NULL) * @return an iterator for the currently stored values */ -cx_attr_nonnull cx_attr_nodiscard cx_attr_export CxMapIterator cxMapMutIteratorValues(CxMap *map); @@ -352,10 +354,9 @@ * @note An iterator iterates over all elements successively. Therefore, the order * highly depends on the map implementation and may change arbitrarily when the contents change. * - * @param map the map to create the iterator for + * @param map the map to create the iterator for (can be @c NULL) * @return an iterator for the currently stored keys */ -cx_attr_nonnull cx_attr_nodiscard cx_attr_export CxMapIterator cxMapMutIteratorKeys(CxMap *map); @@ -369,12 +370,11 @@ * @note An iterator iterates over all elements successively. Therefore, the order * highly depends on the map implementation and may change arbitrarily when the contents change. * - * @param map the map to create the iterator for + * @param map the map to create the iterator for (can be @c NULL) * @return an iterator for the currently stored entries * @see cxMapMutIteratorKeys() * @see cxMapMutIteratorValues() */ -cx_attr_nonnull cx_attr_nodiscard cx_attr_export CxMapIterator cxMapMutIterator(CxMap *map); @@ -387,7 +387,7 @@ CxHashKey const &key, void *value ) { - return map->cl->put(map, key, value); + return map->cl->put(map, key, value) == NULL; } cx_attr_nonnull @@ -396,7 +396,7 @@ cxstring const &key, void *value ) { - return map->cl->put(map, cx_hash_key_cxstr(key), value); + return map->cl->put(map, cx_hash_key_cxstr(key), value) == NULL; } cx_attr_nonnull @@ -405,7 +405,7 @@ cxmutstr const &key, void *value ) { - return map->cl->put(map, cx_hash_key_cxstr(key), value); + return map->cl->put(map, cx_hash_key_cxstr(key), value) == NULL; } cx_attr_nonnull @@ -415,7 +415,40 @@ const char *key, void *value ) { - return map->cl->put(map, cx_hash_key_str(key), value); + return map->cl->put(map, cx_hash_key_str(key), value) == NULL; +} + +cx_attr_nonnull +static inline void *cxMapEmplace( + CxMap *map, + CxHashKey const &key +) { + return map->cl->put(map, key, NULL); +} + +cx_attr_nonnull +static inline void *cxMapEmplace( + CxMap *map, + cxstring const &key +) { + return map->cl->put(map, cx_hash_key_cxstr(key), NULL); +} + +cx_attr_nonnull +static inline void *cxMapEmplace( + CxMap *map, + cxmutstr const &key +) { + return map->cl->put(map, cx_hash_key_cxstr(key), NULL); +} + +cx_attr_nonnull +cx_attr_cstr_arg(2) +static inline void *cxMapEmplace( + CxMap *map, + const char *key +) { + return map->cl->put(map, cx_hash_key_str(key), NULL); } cx_attr_nonnull @@ -540,7 +573,7 @@ CxHashKey key, void *value ) { - return map->cl->put(map, key, value); + return map->cl->put(map, key, value) == NULL; } /** @@ -552,7 +585,7 @@ cxstring key, void *value ) { - return map->cl->put(map, cx_hash_key_cxstr(key), value); + return map->cl->put(map, cx_hash_key_cxstr(key), value) == NULL; } /** @@ -564,7 +597,7 @@ cxmutstr key, void *value ) { - return map->cl->put(map, cx_hash_key_cxstr(key), value); + return map->cl->put(map, cx_hash_key_cxstr(key), value) == NULL; } /** @@ -577,7 +610,7 @@ const char *key, void *value ) { - return map->cl->put(map, cx_hash_key_str(key), value); + return map->cl->put(map, cx_hash_key_str(key), value) == NULL; } /** @@ -608,6 +641,77 @@ (map, key, value) /** + * @copydoc cxMapEmplace() + */ +cx_attr_nonnull +static inline void *cx_map_emplace( + CxMap *map, + CxHashKey key +) { + return map->cl->put(map, key, NULL); +} + +/** + * @copydoc cxMapEmplace() + */ +cx_attr_nonnull +static inline void *cx_map_emplace_cxstr( + CxMap *map, + cxstring key +) { + return map->cl->put(map, cx_hash_key_cxstr(key), NULL); +} + +/** + * @copydoc cxMapEmplace() + */ +cx_attr_nonnull +static inline void *cx_map_emplace_mustr( + CxMap *map, + cxmutstr key +) { + return map->cl->put(map, cx_hash_key_cxstr(key), NULL); +} + +/** + * @copydoc cxMapEmplace() + */ +cx_attr_nonnull +cx_attr_cstr_arg(2) +static inline void *cx_map_emplace_str( + CxMap *map, + const char *key +) { + return map->cl->put(map, cx_hash_key_str(key), NULL); +} + +/** + * Allocates memory for a value in the map associated with the specified key. + * + * A possible existing value will be overwritten. + * If destructor functions are specified, they are called for + * the overwritten element. + * + * If the map is storing pointers, this function returns a @c void** pointer, + * meaning a pointer to that pointer. + * + * The @p key is always copied. + * + * @param map (@c CxMap*) the map + * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key + * @return the pointer to the allocated memory or @c NULL if allocation fails + * @retval zero success + * @retval non-zero value on memory allocation failure + */ +#define cxMapEmplace(map, key) _Generic((key), \ + CxHashKey: cx_map_emplace, \ + cxstring: cx_map_emplace_cxstr, \ + cxmutstr: cx_map_emplace_mustr, \ + char*: cx_map_emplace_str, \ + const char*: cx_map_emplace_str) \ + (map, key) + +/** * @copydoc cxMapGet() */ cx_attr_nonnull
--- a/ucx/cx/mempool.h Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/cx/mempool.h Tue Sep 09 20:56:47 2025 +0200 @@ -43,31 +43,107 @@ extern "C" { #endif -/** Internal structure for pooled memory. */ -struct cx_mempool_memory_s; +/** A memory block in a simple memory pool. */ +struct cx_mempool_memory_s { + /** The destructor. */ + cx_destructor_func destructor; + /** The actual memory. */ + char c[]; +}; + +/** A memory block in an advanced memory pool. */ +struct cx_mempool_memory2_s { + /** The destructor. */ + cx_destructor_func2 destructor; + /** Data for the destructor. */ + void *data; + /** The actual memory. */ + char c[]; +}; + +/** Represents memory that is not allocated by, but registered with a pool. */ +struct cx_mempool_foreign_memory_s { + /** The foreign memory. */ + void* mem; + union { + /** Simple destructor. */ + cx_destructor_func destr; + /** Advanced destructor. */ + cx_destructor_func2 destr2; + }; + /** Data for the advanced destructor. */ + void *destr2_data; +}; + +/** Specifies how individual blocks are allocated. */ +enum cx_mempool_type { + /** + * Allows registration of cx_destructor_func for each memory block. + */ + CX_MEMPOOL_TYPE_SIMPLE, + /** + * Allows registration of cx_destructor_func2 for each memory block. + */ + CX_MEMPOOL_TYPE_ADVANCED, + /** + * No individual destructor registration allowed. + * + * In this mode, no additional memory per block is allocated. + */ + CX_MEMPOOL_TYPE_PURE, +}; /** * The basic structure of a memory pool. * Should be the first member of an actual memory pool implementation. */ struct cx_mempool_s { + /** The used allocator, initialized with the cxDefaultAllocator. */ + const CxAllocator * const base_allocator; + /** The provided allocator. */ const CxAllocator *allocator; - /** - * A destructor that shall be automatically registered for newly allocated memory. - * This destructor MUST NOT free the memory. - */ - cx_destructor_func auto_destr; - /** Array of pooled memory. */ - struct cx_mempool_memory_s **data; + void **data; /** Number of pooled memory items. */ size_t size; /** Memory pool capacity. */ size_t capacity; + + /** Array of registered memory. */ + struct cx_mempool_foreign_memory_s *registered; + + /** Number of registered memory items. */ + size_t registered_size; + + /** Capacity for registered memory. */ + size_t registered_capacity; + + /** + * A destructor that shall be called before deallocating a memory block. + * This destructor MUST NOT free the memory itself. + * + * It is guaranteed that this destructor is called after the individual + * destructor of the memory block and before @c destr2. + */ + cx_destructor_func destr; + + /** + * A destructor that shall be called before deallocating a memory block. + * This destructor MUST NOT free the memory itself. + * + * It is guaranteed that this destructor is called after the individual + * destructor of the memory block and @c destr. + */ + cx_destructor_func2 destr2; + + /** + * Additional data for the @c destr2. + */ + void *destr2_data; }; /** @@ -84,31 +160,76 @@ void cxMempoolFree(CxMempool *pool); /** - * Creates an array-based memory pool with a shared destructor function. + * Creates an array-based memory pool. * - * This destructor MUST NOT free the memory. + * The type determines how much additional memory is allocated per block + * to register a destructor function. * - * @param capacity the initial capacity of the pool - * @param destr optional destructor function to use for allocated memory + * @param capacity the initial capacity of the pool (an implementation default if zero) + * @param type the type of memory pool * @return the created memory pool or @c NULL if allocation failed */ cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxMempoolFree, 1) cx_attr_export -CxMempool *cxMempoolCreate(size_t capacity, cx_destructor_func destr); +CxMempool *cxMempoolCreate(size_t capacity, enum cx_mempool_type type); + +/** + * Creates a basic array-based memory pool. + * + * Convenience macro to create a memory pool of type #CX_MEMPOOL_TYPE_SIMPLE. + * + * @param capacity (@c size_t) the initial capacity of the pool + * @return (@c CxMempool*) the created memory pool or @c NULL if allocation failed + */ +#define cxMempoolCreateSimple(capacity) cxMempoolCreate(capacity, CX_MEMPOOL_TYPE_SIMPLE) + +/** + * Creates a basic array-based memory pool. + * + * Convenience macro to create a memory pool of type #CX_MEMPOOL_TYPE_ADVANCED. + * + * @param capacity (@c size_t) the initial capacity of the pool + * @return (@c CxMempool*) the created memory pool or @c NULL if allocation failed + */ +#define cxMempoolCreateAdvanced(capacity) cxMempoolCreate(capacity, CX_MEMPOOL_TYPE_ADVANCED) /** * Creates a basic array-based memory pool. * + * Convenience macro to create a memory pool of type #CX_MEMPOOL_TYPE_PURE. + * * @param capacity (@c size_t) the initial capacity of the pool * @return (@c CxMempool*) the created memory pool or @c NULL if allocation failed */ -#define cxMempoolCreateSimple(capacity) cxMempoolCreate(capacity, NULL) +#define cxMempoolCreatePure(capacity) cxMempoolCreate(capacity, CX_MEMPOOL_TYPE_PURE) + +/** + * Sets the global destructor for all memory blocks within the specified pool. + * + * @param pool the memory pool + * @param fnc the destructor that shall be applied to all memory blocks + */ +cx_attr_nonnull_arg(1) +cx_attr_export +void cxMempoolGlobalDestructor(CxMempool *pool, cx_destructor_func fnc); + +/** + * Sets the global destructor for all memory blocks within the specified pool. + * + * @param pool the memory pool + * @param fnc the destructor that shall be applied to all memory blocks + * @param data additional data for the destructor function + */ +cx_attr_nonnull_arg(1) +cx_attr_export +void cxMempoolGlobalDestructor2(CxMempool *pool, cx_destructor_func2 fnc, void *data); /** * Sets the destructor function for a specific allocated memory object. * + * If the type of memory pool is not #CX_MEMPOOL_TYPE_SIMPLE, the behavior is undefined. * If the memory is not managed by a UCX memory pool, the behavior is undefined. * The destructor MUST NOT free the memory. * @@ -123,10 +244,29 @@ ); /** + * Sets the destructor function for a specific allocated memory object. + * + * If the type of memory pool is not #CX_MEMPOOL_TYPE_ADVANCED, the behavior is undefined. + * If the memory is not managed by a UCX memory pool, the behavior is undefined. + * The destructor MUST NOT free the memory. + * + * @param memory the object allocated in the pool + * @param fnc the destructor function + * @param data additional data for the destructor function + */ +cx_attr_nonnull +cx_attr_export +void cxMempoolSetDestructor2( + void *memory, + cx_destructor_func2 fnc, + void *data +); + +/** * Removes the destructor function for a specific allocated memory object. * + * If the type of memory pool is not #CX_MEMPOOL_TYPE_SIMPLE, the behavior is undefined. * If the memory is not managed by a UCX memory pool, the behavior is undefined. - * The destructor MUST NOT free the memory. * * @param memory the object allocated in the pool */ @@ -135,12 +275,25 @@ void cxMempoolRemoveDestructor(void *memory); /** + * Removes the destructor function for a specific allocated memory object. + * + * If the type of memory pool is not #CX_MEMPOOL_TYPE_ADVANCED, the behavior is undefined. + * If the memory is not managed by a UCX memory pool, the behavior is undefined. + * + * @param memory the object allocated in the pool + */ +cx_attr_nonnull +cx_attr_export +void cxMempoolRemoveDestructor2(void *memory); + +/** * Registers foreign memory with this pool. * * The destructor, in contrast to memory allocated by the pool, MUST free the memory. + * This function can be used with any pool of any type, since destructors for registered memory + * are entirely independent of the pool's memory management. * - * A small portion of memory will be allocated to register the information in the pool. - * If that allocation fails, this function will return non-zero. + * The destructor for the registered memory will be called after all pooled items have been freed. * * @param pool the pool * @param memory the object to register (MUST NOT be already allocated in the pool) @@ -156,6 +309,79 @@ cx_destructor_func destr ); + +/** + * Registers foreign memory with this pool. + * + * The destructor, in contrast to memory allocated by the pool, MUST free the memory. + * This function can be used with any pool of any type, since destructors for registered memory + * are entirely independent of the pool's memory management. + * + * The destructor for the registered memory will be called after all pooled items have been freed. + * + * @attention The data pointer MUST NOT be @c NULL. + * If you wish to register a destructor without additional data, use cxMempoolRegister(). + * + * @param pool the pool + * @param memory the object to register (MUST NOT be already allocated in the pool) + * @param destr the destructor function + * @param data additional data for the destructor function + * @retval zero success + * @retval non-zero failure + */ +cx_attr_nonnull +cx_attr_export +int cxMempoolRegister2( + CxMempool *pool, + void *memory, + cx_destructor_func2 destr, + void *data +); + +/** + * Transfers all the memory managed by one pool to another. + * + * The allocator of the source pool will also be transferred and registered with the destination pool + * and stays valid, as long as the destination pool is not destroyed. + * + * The source pool will get a completely new allocator and can be reused or destroyed afterward. + * + * This function fails when the destination pool has a different type than the source pool. + * + * @param source the pool to move the memory from + * @param dest the pool where to transfer the memory to + * @retval zero success + * @retval non-zero allocation failure or incompatible pools + */ +cx_attr_nonnull +cx_attr_export +int cxMempoolTransfer( + CxMempool *source, + CxMempool *dest +); + +/** + * Transfers an object from one pool to another. + * + * This function fails when the destination pool has a different type than the source pool. + * + * @attention If the object maintains a reference to the pool's allocator, + * you must make sure to update that reference to the allocator of the destination pool. + * + * @param source the pool to move the memory from + * @param dest the pool where to transfer the memory to + * @param obj pointer to the object that shall be transferred + * @retval zero success + * @retval non-zero failure, or the object was not found in the source pool, or the pools are incompatible + */ +cx_attr_nonnull +cx_attr_export +int cxMempoolTransferObject( + CxMempool *source, + CxMempool *dest, + const void *obj +); + #ifdef __cplusplus } // extern "C" #endif
--- a/ucx/cx/printf.h Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/cx/printf.h Tue Sep 09 20:56:47 2025 +0200 @@ -229,7 +229,7 @@ cx_attr_cstr_arg(4) cx_attr_export int cx_sprintf_a( - CxAllocator *alloc, + const CxAllocator *alloc, char **str, size_t *len, const char *fmt, @@ -274,7 +274,7 @@ cx_attr_access_rw(3) cx_attr_export int cx_vsprintf_a( - CxAllocator *alloc, + const CxAllocator *alloc, char **str, size_t *len, const char *fmt, @@ -333,7 +333,7 @@ cx_attr_access_rw(4) cx_attr_export int cx_sprintf_sa( - CxAllocator *alloc, + const CxAllocator *alloc, char *buf, size_t *len, char **str, @@ -388,7 +388,7 @@ cx_attr_cstr_arg(5) cx_attr_export int cx_vsprintf_sa( - CxAllocator *alloc, + const CxAllocator *alloc, char *buf, size_t *len, char **str,
--- a/ucx/cx/properties.h Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/cx/properties.h Tue Sep 09 20:56:47 2025 +0200 @@ -551,10 +551,12 @@ /** * Creates a properties sink for an UCX map. * - * The values stored in the map will be pointers to strings allocated - * by #cx_strdup_a(). - * The default stdlib allocator will be used, unless you specify a custom - * allocator in the optional @c data of the sink. + * The values stored in the map will be pointers to freshly allocated, + * zero-terminated C strings (@c char*), which means the @p map should have been + * created with #CX_STORE_POINTERS. + * + * The cxDefaultAllocator will be used unless you specify a custom + * allocator in the optional @c data field of the returned sink. * * @param map the map that shall consume the k/v-pairs. * @return the sink
--- a/ucx/cx/string.h Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/cx/string.h Tue Sep 09 20:56:47 2025 +0200 @@ -39,6 +39,12 @@ #include "common.h" #include "allocator.h" +/** Expands a UCX string as printf arguments. */ +#define CX_SFMT(s) (int) (s).length, (s).ptr + +/** Format specifier for a UCX string */ +#define CX_PRIstr ".*s" + /** * The maximum length of the "needle" in cx_strstr() that can use SBO. */ @@ -161,6 +167,8 @@ * * The length is implicitly inferred by using a call to @c strlen(). * + * When @c NULL is passed, the length will be set to zero. + * * @note the wrapped string will share the specified pointer to the string. * If you do want a copy, use cx_strdup() on the return value of this function. * @@ -171,7 +179,6 @@ * * @see cx_mutstrn() */ -cx_attr_nonnull cx_attr_nodiscard cx_attr_cstr_arg(1) cx_attr_export @@ -206,6 +213,8 @@ * * The length is implicitly inferred by using a call to @c strlen(). * + * When @c NULL is passed, the length will be set to zero. + * * @note the wrapped string will share the specified pointer to the string. * If you do want a copy, use cx_strdup() on the return value of this function. * @@ -216,7 +225,6 @@ * * @see cx_strn() */ -cx_attr_nonnull cx_attr_nodiscard cx_attr_cstr_arg(1) cx_attr_export @@ -257,6 +265,10 @@ static inline cxstring cx_strcast(cxstring str) { return str; } +cx_attr_nodiscard +static inline cxstring cx_strcast(const char *str) { + return cx_str(str); +} extern "C" { #else /** @@ -281,6 +293,17 @@ } /** + * Internal function, do not use. + * @param str + * @return + * @see cx_strcast() + */ +cx_attr_nodiscard +static inline cxstring cx_strcast_z(const char *str) { + return cx_str(str); +} + +/** * Casts a mutable string to an immutable string. * * Does nothing for already immutable strings. @@ -294,12 +317,13 @@ */ #define cx_strcast(str) _Generic((str), \ cxmutstr: cx_strcast_m, \ - cxstring: cx_strcast_c) \ - (str) + cxstring: cx_strcast_c, \ + const char*: cx_strcast_z, \ + char *: cx_strcast_z) (str) #endif /** - * Passes the pointer in this string to @c free(). + * Passes the pointer in this string to the cxDefaultAllocator's @c free() function. * * The pointer in the struct is set to @c NULL and the length is set to zero * which means that this function protects you against double-free. @@ -334,6 +358,46 @@ ); /** + * Copies a string. + * + * The memory in the @p dest structure is either allocated or re-allocated to fit the entire + * source string, including a zero-terminator. + * + * The string in @p dest is guaranteed to be zero-terminated, regardless of whether @p src is. + * + * @param alloc the allocator + * @param dest a pointer to the structure where to copy the contents to + * @param src the source string + * + * @retval zero success + * @retval non-zero if re-allocation failed + */ +cx_attr_nonnull_arg(1) +cx_attr_export +int cx_strcpy_a( + const CxAllocator *alloc, + cxmutstr *dest, + cxstring src +); + + +/** + * Copies a string. + * + * The memory in the @p dest structure is either allocated or re-allocated to fit the entire + * source string, including a zero-terminator. + * + * The string in @p dest is guaranteed to be zero-terminated, regardless of whether @p src is. + * + * @param dest (@c cxmutstr*) a pointer to the structure where to copy the contents to + * @param src (@c cxstring) the source string + * + * @retval zero success + * @retval non-zero if re-allocation failed + */ +#define cx_strcpy(dest, src) cx_strcpy_a(cxDefaultAllocator, dest, src) + +/** * Returns the accumulated length of all specified strings. * * If this sum overflows, errno is set to EOVERFLOW. @@ -408,7 +472,7 @@ /** * Concatenates strings and returns a new string. * - * The resulting string will be allocated by standard @c malloc(). + * The resulting string will be allocated by the cxDefaultAllocator. * So developers @em must pass the return value to cx_strfree() eventually. * * If memory allocation fails, the pointer in the returned string will @@ -428,7 +492,7 @@ /** * Concatenates strings. * - * The resulting string will be allocated by standard @c malloc(). + * The resulting string will be allocated by the cxDefaultAllocator. * So developers @em must pass the return value to cx_strfree() eventually. * * If @p str already contains a string, the memory will be reallocated and @@ -884,8 +948,8 @@ /** * Creates a duplicate of the specified string. * - * The new string will contain a copy allocated by standard - * @c malloc(). So developers @em must pass the return value to cx_strfree(). + * The new string will contain a copy allocated by the cxDefaultAllocator. + * So developers @em must pass the return value to cx_strfree(). * * @note The returned string is guaranteed to be zero-terminated. * @@ -1016,7 +1080,7 @@ * * Replaces at most @p replmax occurrences. * - * The returned string will be allocated by @c malloc() and is guaranteed + * The returned string will be allocated by the cxDefaultAllocator and is guaranteed * to be zero-terminated. * * If allocation fails, or the input string is empty, @@ -1052,7 +1116,7 @@ /** * Replaces a string with another string. * - * The returned string will be allocated by @c malloc() and is guaranteed + * The returned string will be allocated by the cxDefaultAllocator and is guaranteed * to be zero-terminated. * * If allocation fails, or the input string is empty,
--- a/ucx/cx/tree.h Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/cx/tree.h Tue Sep 09 20:56:47 2025 +0200 @@ -120,6 +120,7 @@ size_t stack_size; /** * The current depth in the tree. + * The node with which the iteration starts has depth 1. */ size_t depth; }; @@ -135,6 +136,7 @@ void *node; /** * The depth of the node. + * The first visited node has depth 1. */ size_t depth; /** @@ -211,7 +213,7 @@ */ cx_attr_nonnull static inline void cxTreeIteratorDispose(CxTreeIterator *iter) { - free(iter->stack); + cxFreeDefault(iter->stack); iter->stack = NULL; } @@ -224,7 +226,7 @@ struct cx_tree_visitor_queue_s *q = visitor->queue_next; while (q != NULL) { struct cx_tree_visitor_queue_s *next = q->next; - free(q); + cxFreeDefault(q); q = next; } } @@ -441,7 +443,7 @@ * Creates a depth-first iterator for a tree with the specified root node. * * @note A tree iterator needs to maintain a stack of visited nodes, which is - * allocated using stdlib malloc(). + * allocated using the cxDefaultAllocator. * When the iterator becomes invalid, this memory is automatically released. * However, if you wish to cancel the iteration before the iterator becomes * invalid by itself, you MUST call cxTreeIteratorDispose() manually to release @@ -469,8 +471,8 @@ /** * Creates a breadth-first iterator for a tree with the specified root node. * - * @note A tree visitor needs to maintain a queue of to be visited nodes, which - * is allocated using stdlib malloc(). + * @note A tree visitor needs to maintain a queue of to-be visited nodes, which + * is allocated using the cxDefaultAllocator. * When the visitor becomes invalid, this memory is automatically released. * However, if you wish to cancel the iteration before the visitor becomes * invalid by itself, you MUST call cxTreeVisitorDispose() manually to release @@ -956,7 +958,7 @@ * will free the nodes with the allocator's free() method. * * @param allocator the allocator that shall be used - * (if @c NULL, a default stdlib allocator will be used) + * (if @c NULL, the cxDefaultAllocator will be used) * @param create_func a function that creates new nodes * @param search_func a function that compares two nodes * @param search_data_func a function that compares a node with data @@ -1020,7 +1022,7 @@ * tree, you need to specify those functions afterwards. * * @param allocator the allocator that was used for nodes of the wrapped tree - * (if @c NULL, a default stdlib allocator is assumed) + * (if @c NULL, the cxDefaultAllocator is assumed) * @param root the root node of the tree that shall be wrapped * @param loc_parent offset in the node struct for the parent pointer * @param loc_children offset in the node struct for the children linked list @@ -1188,6 +1190,18 @@ size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root); /** + * Determines the size of the entire tree. + * + * @param tree the tree + * @return the tree size, counting the root as one + */ +cx_attr_nonnull +cx_attr_nodiscard +static inline size_t cxTreeSize(CxTree *tree) { + return tree->size; +} + +/** * Determines the depth of the entire tree. * * @param tree the tree
--- a/ucx/cx/utils.h Tue Sep 09 16:01:30 2025 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,194 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * \file utils.h - * - * \brief General purpose utility functions. - * - * \author Mike Becker - * \author Olaf Wintermann - * \copyright 2-Clause BSD License - */ - -#ifndef UCX_UTILS_H -#define UCX_UTILS_H - -#include "common.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * Convenience macro for a for loop that counts from zero to n-1. - */ -#define cx_for_n(varname, n) for (size_t varname = 0 ; (varname) < (n) ; (varname)++) - -/** - * Convenience macro for swapping two pointers. - */ -#ifdef __cplusplus -#define cx_swap_ptr(left, right) do {auto cx_tmp_swap_var = left; left = right; right = cx_tmp_swap_var;} while(0) -#else -#define cx_swap_ptr(left, right) do {void *cx_tmp_swap_var = left; left = right; right = cx_tmp_swap_var;} while(0) -#endif - -// cx_szmul() definition - -#if (__GNUC__ >= 5 || defined(__clang__)) && !defined(CX_NO_SZMUL_BUILTIN) -#define CX_SZMUL_BUILTIN - -/** - * Alias for \c __builtin_mul_overflow. - * - * Performs a multiplication of size_t values and checks for overflow. - * - * @param a first operand - * @param b second operand - * @param result a pointer to a size_t, where the result should - * be stored - * @return zero, if no overflow occurred and the result is correct, non-zero - * otherwise - */ -#define cx_szmul(a, b, result) __builtin_mul_overflow(a, b, result) - -#else // no GNUC or clang bultin - -/** - * Performs a multiplication of size_t values and checks for overflow. - * - * @param a first operand - * @param b second operand - * @param result a pointer to a size_t, where the result should - * be stored - * @return zero, if no overflow occurred and the result is correct, non-zero - * otherwise - */ -#define cx_szmul(a, b, result) cx_szmul_impl(a, b, result) - -/** - * Performs a multiplication of size_t values and checks for overflow. - * - * This is a custom implementation in case there is no compiler builtin - * available. - * - * @param a first operand - * @param b second operand - * @param result a pointer to a size_t where the result should be stored - * @return zero, if no overflow occurred and the result is correct, non-zero - * otherwise - */ -int cx_szmul_impl(size_t a, size_t b, size_t *result); - -#endif // cx_szmul - - -/** - * Reads data from a stream and writes it to another stream. - * - * @param src the source stream - * @param dest the destination stream - * @param rfnc the read function - * @param wfnc the write function - * @param buf a pointer to the copy buffer or \c NULL if a buffer - * shall be implicitly created on the heap - * @param bufsize the size of the copy buffer - if \p buf is \c NULL you can - * set this to zero to let the implementation decide - * @param n the maximum number of bytes that shall be copied. - * If this is larger than \p bufsize, the content is copied over multiple - * iterations. - * @return the total number of bytes copied - */ -__attribute__((__nonnull__(1, 2, 3, 4))) -size_t cx_stream_bncopy( - void *src, - void *dest, - cx_read_func rfnc, - cx_write_func wfnc, - char *buf, - size_t bufsize, - size_t n -); - -/** - * Reads data from a stream and writes it to another stream. - * - * @param src the source stream - * @param dest the destination stream - * @param rfnc the read function - * @param wfnc the write function - * @param buf a pointer to the copy buffer or \c NULL if a buffer - * shall be implicitly created on the heap - * @param bufsize the size of the copy buffer - if \p buf is \c NULL you can - * set this to zero to let the implementation decide - * @return total number of bytes copied - */ -#define cx_stream_bcopy(src, dest, rfnc, wfnc, buf, bufsize) \ - cx_stream_bncopy(src, dest, rfnc, wfnc, buf, bufsize, SIZE_MAX) - -/** - * Reads data from a stream and writes it to another stream. - * - * The data is temporarily stored in a stack allocated buffer. - * - * @param src the source stream - * @param dest the destination stream - * @param rfnc the read function - * @param wfnc the write function - * @param n the maximum number of bytes that shall be copied. - * @return total number of bytes copied - */ -__attribute__((__nonnull__)) -size_t cx_stream_ncopy( - void *src, - void *dest, - cx_read_func rfnc, - cx_write_func wfnc, - size_t n -); - -/** - * Reads data from a stream and writes it to another stream. - * - * The data is temporarily stored in a stack allocated buffer. - * - * @param src the source stream - * @param dest the destination stream - * @param rfnc the read function - * @param wfnc the write function - * @return total number of bytes copied - */ -#define cx_stream_copy(src, dest, rfnc, wfnc) \ - cx_stream_ncopy(src, dest, rfnc, wfnc, SIZE_MAX) - -#ifdef __cplusplus -} -#endif - -#endif // UCX_UTILS_H
--- a/ucx/hash_key.c Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/hash_key.c Tue Sep 09 20:56:47 2025 +0200 @@ -62,14 +62,14 @@ switch (len) { case 3: h ^= (data[i + 2] & 0xFF) << 16; - __attribute__((__fallthrough__)); + cx_attr_fallthrough; case 2: h ^= (data[i + 1] & 0xFF) << 8; - __attribute__((__fallthrough__)); + cx_attr_fallthrough; case 1: h ^= (data[i + 0] & 0xFF); h *= m; - __attribute__((__fallthrough__)); + cx_attr_fallthrough; default: // do nothing ; }
--- a/ucx/hash_map.c Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/hash_map.c Tue Sep 09 20:56:47 2025 +0200 @@ -78,7 +78,7 @@ cxFree(map->collection.allocator, map); } -static int cx_hash_map_put( +static void *cx_hash_map_put( CxMap *map, CxHashKey key, void *value @@ -105,7 +105,9 @@ memcmp(elm->key.data, key.data, key.len) == 0) { // overwrite existing element, but call destructors first cx_invoke_destructor(map, elm->data); - if (map->collection.store_pointer) { + if (value == NULL) { + memset(elm->data, 0, map->collection.elem_size); + } else if (map->collection.store_pointer) { memcpy(elm->data, &value, sizeof(void *)); } else { memcpy(elm->data, value, map->collection.elem_size); @@ -116,10 +118,12 @@ allocator, sizeof(struct cx_hash_map_element_s) + map->collection.elem_size ); - if (e == NULL) return -1; + if (e == NULL) return NULL; // write the value - if (map->collection.store_pointer) { + if (value == NULL) { + memset(e->data, 0, map->collection.elem_size); + } else if (map->collection.store_pointer) { memcpy(e->data, &value, sizeof(void *)); } else { memcpy(e->data, value, map->collection.elem_size); @@ -127,7 +131,10 @@ // copy the key void *kd = cxMalloc(allocator, key.len); - if (kd == NULL) return -1; + if (kd == NULL) { + cxFree(allocator, e); + return NULL; + } memcpy(kd, key.data, key.len); e->key.data = kd; e->key.len = key.len; @@ -140,12 +147,14 @@ prev->next = e; } e->next = elm; + elm = e; // increase the size map->collection.size++; } - return 0; + // return pointer to the element + return elm->data; } static void cx_hash_map_unlink(
--- a/ucx/json.c Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/json.c Tue Sep 09 20:56:47 2025 +0200 @@ -46,22 +46,17 @@ return cx_strcmp(cx_strcast(left->name), cx_strcast(right->name)); } -static CxJsonObjValue *json_find_objvalue(const CxJsonValue *obj, cxstring name) { +static size_t json_find_objvalue(const CxJsonValue *obj, cxstring name) { assert(obj->type == CX_JSON_OBJECT); CxJsonObjValue kv_dummy; kv_dummy.name = cx_mutstrn((char*) name.ptr, name.length); - size_t index = cx_array_binary_search( + return cx_array_binary_search( obj->value.object.values, obj->value.object.values_size, sizeof(CxJsonObjValue), &kv_dummy, json_cmp_objvalue ); - if (index == obj->value.object.values_size) { - return NULL; - } else { - return &obj->value.object.values[index]; - } } static int json_add_objvalue(CxJsonValue *objv, CxJsonObjValue member) { @@ -500,7 +495,7 @@ if (all_printable && escape) { size_t capa = str.length + 32; - char *space = malloc(capa); + char *space = cxMallocDefault(capa); if (space == NULL) return cx_mutstrn(NULL, 0); cxBufferInit(&buf, space, capa, NULL, CX_BUFFER_AUTO_EXTEND); cxBufferWrite(str.ptr, 1, i, &buf); @@ -631,10 +626,10 @@ void cxJsonDestroy(CxJson *json) { cxBufferDestroy(&json->buffer); if (json->states != json->states_internal) { - free(json->states); + cxFreeDefault(json->states); } if (json->vbuf != json->vbuf_internal) { - free(json->vbuf); + cxFreeDefault(json->vbuf); } cxJsonValueFree(json->parsed); json->parsed = NULL; @@ -984,67 +979,67 @@ if (values[i] == NULL) break; cxJsonValueFree(values[i]); } - free(values); + cxFreeDefault(values); } // LCOV_EXCL_STOP int cxJsonArrAddNumbers(CxJsonValue* arr, const double* num, size_t count) { - CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); + CxJsonValue** values = cxCallocDefault(count, sizeof(CxJsonValue*)); if (values == NULL) return -1; for (size_t i = 0; i < count; i++) { values[i] = cxJsonCreateNumber(arr->allocator, num[i]); if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; } } int ret = cxJsonArrAddValues(arr, values, count); - free(values); + cxFreeDefault(values); return ret; } int cxJsonArrAddIntegers(CxJsonValue* arr, const int64_t* num, size_t count) { - CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); + CxJsonValue** values = cxCallocDefault(count, sizeof(CxJsonValue*)); if (values == NULL) return -1; for (size_t i = 0; i < count; i++) { values[i] = cxJsonCreateInteger(arr->allocator, num[i]); if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; } } int ret = cxJsonArrAddValues(arr, values, count); - free(values); + cxFreeDefault(values); return ret; } int cxJsonArrAddStrings(CxJsonValue* arr, const char* const* str, size_t count) { - CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); + CxJsonValue** values = cxCallocDefault(count, sizeof(CxJsonValue*)); if (values == NULL) return -1; for (size_t i = 0; i < count; i++) { values[i] = cxJsonCreateString(arr->allocator, str[i]); if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; } } int ret = cxJsonArrAddValues(arr, values, count); - free(values); + cxFreeDefault(values); return ret; } int cxJsonArrAddCxStrings(CxJsonValue* arr, const cxstring* str, size_t count) { - CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); + CxJsonValue** values = cxCallocDefault(count, sizeof(CxJsonValue*)); if (values == NULL) return -1; for (size_t i = 0; i < count; i++) { values[i] = cxJsonCreateCxString(arr->allocator, str[i]); if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; } } int ret = cxJsonArrAddValues(arr, values, count); - free(values); + cxFreeDefault(values); return ret; } int cxJsonArrAddLiterals(CxJsonValue* arr, const CxJsonLiteral* lit, size_t count) { - CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); + CxJsonValue** values = cxCallocDefault(count, sizeof(CxJsonValue*)); if (values == NULL) return -1; for (size_t i = 0; i < count; i++) { values[i] = cxJsonCreateLiteral(arr->allocator, lit[i]); if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; } } int ret = cxJsonArrAddValues(arr, values, count); - free(values); + cxFreeDefault(values); return ret; } @@ -1126,6 +1121,20 @@ return value->value.array.array[index]; } +CxJsonValue *cxJsonArrRemove(CxJsonValue *value, size_t index) { + if (index >= value->value.array.array_size) { + return NULL; + } + CxJsonValue *ret = value->value.array.array[index]; + // TODO: replace with a low level cx_array_remove() + size_t count = value->value.array.array_size - index - 1; + if (count > 0) { + memmove(value->value.array.array + index, value->value.array.array + index + 1, count * sizeof(CxJsonValue*)); + } + value->value.array.array_size--; + return ret; +} + CxIterator cxJsonArrIter(const CxJsonValue *value) { return cxIteratorPtr( value->value.array.array, @@ -1142,11 +1151,25 @@ } CxJsonValue *cx_json_obj_get_cxstr(const CxJsonValue *value, cxstring name) { - CxJsonObjValue *member = json_find_objvalue(value, name); - if (member == NULL) { + size_t index = json_find_objvalue(value, name); + if (index >= value->value.object.values_size) { return &cx_json_value_nothing; } else { - return member->value; + return value->value.object.values[index].value; + } +} + +CxJsonValue *cx_json_obj_remove_cxstr(CxJsonValue *value, cxstring name) { + size_t index = json_find_objvalue(value, name); + if (index >= value->value.object.values_size) { + return NULL; + } else { + CxJsonObjValue kv = value->value.object.values[index]; + cx_strfree_a(value->allocator, &kv.name); + // TODO: replace with cx_array_remove() + value->value.object.values_size--; + memmove(value->value.object.values + index, value->value.object.values + index + 1, (value->value.object.values_size - index) * sizeof(CxJsonObjValue)); + return kv.value; } }
--- a/ucx/linked_list.c Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/linked_list.c Tue Sep 09 20:56:47 2025 +0200 @@ -401,7 +401,7 @@ ) { void *sbo[CX_LINKED_LIST_SORT_SBO_SIZE]; void **sorted = length >= CX_LINKED_LIST_SORT_SBO_SIZE ? - malloc(sizeof(void *) * length) : sbo; + cxMallocDefault(sizeof(void *) * length) : sbo; if (sorted == NULL) abort(); void *rc, *lc; @@ -439,7 +439,7 @@ *begin = sorted[0]; *end = sorted[length - 1]; if (sorted != sbo) { - free(sorted); + cxFreeDefault(sorted); } } @@ -565,6 +565,7 @@ // HIGH LEVEL LINKED LIST IMPLEMENTATION typedef struct cx_linked_list_node cx_linked_list_node; +// IMPORTANT: the layout must stay exactly like this, because kv_list.c uses that! struct cx_linked_list_node { cx_linked_list_node *prev; cx_linked_list_node *next; @@ -613,7 +614,9 @@ // initialize new new_node new_node->prev = new_node->next = NULL; - memcpy(new_node->payload, elem, list->collection.elem_size); + if (elem != NULL) { + memcpy(new_node->payload, elem, list->collection.elem_size); + } // insert cx_linked_list *ll = (cx_linked_list *) list; @@ -659,12 +662,26 @@ return n; } -static int cx_ll_insert_element( +static void *cx_ll_insert_element( struct cx_list_s *list, size_t index, const void *element ) { - return 1 != cx_ll_insert_array(list, index, element, 1); + // out-of-bounds check + if (index > list->collection.size) return NULL; + + // find position efficiently + cx_linked_list_node *node = index == 0 ? NULL : cx_ll_node_at((cx_linked_list *) list, index - 1); + + // perform first insert + if (cx_ll_insert_at(list, node, element)) return NULL; + + // return a pointer to the data of the inserted node + if (node == NULL) { + return ((cx_linked_list *) list)->begin->payload; + } else { + return node->next->payload; + } } static _Thread_local cx_compare_func cx_ll_insert_sorted_cmp_func; @@ -920,6 +937,8 @@ const void *elem, bool remove ) { + if (list->collection.size == 0) return 0; + size_t index; cx_linked_list *ll = ((cx_linked_list *) list); cx_linked_list_node *node = cx_linked_list_find( @@ -1054,12 +1073,12 @@ } return result; } else { - int result = cx_ll_insert_element(list, list->collection.size, elem); - if (result == 0) { - iter->elem_count++; - iter->index = list->collection.size; + if (cx_ll_insert_element(list, list->collection.size, elem) == NULL) { + return 1; } - return result; + iter->elem_count++; + iter->index = list->collection.size; + return 0; } }
--- a/ucx/list.c Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/list.c Tue Sep 09 20:56:47 2025 +0200 @@ -62,7 +62,7 @@ list->climpl->deallocate(list); } -static int cx_pl_insert_element( +static void *cx_pl_insert_element( struct cx_list_s *list, size_t index, const void *element @@ -282,7 +282,7 @@ const char *src = data; size_t i = 0; for (; i < n; i++) { - if (0 != invoke_list_func( + if (NULL == invoke_list_func( insert_element, list, index + i, src + (i * elem_size))) return i; } @@ -329,7 +329,7 @@ // insert the elements at location si if (ins == 1) { - if (0 != invoke_list_func( + if (NULL == invoke_list_func( insert_element, list, di, src)) return inserted; } else { size_t r = invoke_list_func(insert_array, list, di, src, ins); @@ -354,7 +354,7 @@ void cx_list_default_sort(struct cx_list_s *list) { size_t elem_size = list->collection.elem_size; size_t list_size = list->collection.size; - void *tmp = malloc(elem_size * list_size); + void *tmp = cxMallocDefault(elem_size * list_size); if (tmp == NULL) abort(); // copy elements from source array @@ -377,7 +377,7 @@ loc += elem_size; } - free(tmp); + cxFreeDefault(tmp); } int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j) { @@ -387,7 +387,7 @@ size_t elem_size = list->collection.elem_size; - void *tmp = malloc(elem_size); + void *tmp = cxMallocDefault(elem_size); if (tmp == NULL) return 1; void *ip = invoke_list_func(at, list, i); @@ -397,7 +397,7 @@ memcpy(ip, jp, elem_size); memcpy(jp, tmp, elem_size); - free(tmp); + cxFreeDefault(tmp); return 0; } @@ -476,6 +476,7 @@ CxList *list, size_t index ) { + if (list == NULL) list = cxEmptyList; CxIterator it = list->cl->iterator(list, index, false); it.base.mutating = true; return it; @@ -485,6 +486,7 @@ CxList *list, size_t index ) { + if (list == NULL) list = cxEmptyList; CxIterator it = list->cl->iterator(list, index, true); it.base.mutating = true; return it; @@ -494,3 +496,24 @@ if (list == NULL) return; list->cl->deallocate(list); } + +int cxListSet( + CxList *list, + size_t index, + const void *elem +) { + if (index >= list->collection.size) { + return 1; + } + + if (list->collection.store_pointer) { + // For pointer collections, always use climpl + void **target = list->climpl->at(list, index); + *target = (void *)elem; + } else { + void *target = list->cl->at(list, index); + memcpy(target, elem, list->collection.elem_size); + } + + return 0; +}
--- a/ucx/map.c Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/map.c Tue Sep 09 20:56:47 2025 +0200 @@ -85,18 +85,21 @@ // </editor-fold> CxMapIterator cxMapMutIteratorValues(CxMap *map) { + if (map == NULL) map = cxEmptyMap; CxMapIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_VALUES); it.base.mutating = true; return it; } CxMapIterator cxMapMutIteratorKeys(CxMap *map) { + if (map == NULL) map = cxEmptyMap; CxMapIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_KEYS); it.base.mutating = true; return it; } CxMapIterator cxMapMutIterator(CxMap *map) { + if (map == NULL) map = cxEmptyMap; CxMapIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS); it.base.mutating = true; return it;
--- a/ucx/mempool.c Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/mempool.c Tue Sep 09 20:56:47 2025 +0200 @@ -31,44 +31,69 @@ #include <string.h> #include <errno.h> -struct cx_mempool_memory_s { - /** The destructor. */ - cx_destructor_func destructor; - /** The actual memory. */ - char c[]; -}; +static int cx_mempool_ensure_capacity( + struct cx_mempool_s *pool, + size_t needed_capacity +) { + if (needed_capacity <= pool->capacity) return 0; + size_t newcap = pool->capacity >= 1000 ? + pool->capacity + 1000 : pool->capacity * 2; + size_t newmsize; + // LCOV_EXCL_START + if (pool->capacity > newcap + || cx_szmul(newcap, sizeof(void*), &newmsize)) { + errno = EOVERFLOW; + return 1; + } // LCOV_EXCL_STOP + void **newdata = cxRealloc(pool->base_allocator, pool->data, newmsize); + if (newdata == NULL) return 1; + pool->data = newdata; + pool->capacity = newcap; + return 0; +} -static void *cx_mempool_malloc( +static int cx_mempool_ensure_registered_capacity( + struct cx_mempool_s *pool, + size_t needed_capacity +) { + if (needed_capacity <= pool->registered_capacity) return 0; + // we do not expect so many registrations + size_t newcap = pool->registered_capacity + 8; + size_t newmsize; + // LCOV_EXCL_START + if (pool->registered_capacity > newcap || cx_szmul(newcap, + sizeof(struct cx_mempool_foreign_memory_s), &newmsize)) { + errno = EOVERFLOW; + return 1; + } // LCOV_EXCL_STOP + void *newdata = cxRealloc(pool->base_allocator, pool->registered, newmsize); + if (newdata == NULL) return 1; + pool->registered = newdata; + pool->registered_capacity = newcap; + return 0; +} + +static void *cx_mempool_malloc_simple( void *p, size_t n ) { struct cx_mempool_s *pool = p; - if (pool->size >= pool->capacity) { - size_t newcap = pool->capacity - (pool->capacity % 16) + 16; - size_t newmsize; - if (pool->capacity > newcap || cx_szmul(newcap, - sizeof(struct cx_mempool_memory_s*), &newmsize)) { - errno = EOVERFLOW; - return NULL; - } - struct cx_mempool_memory_s **newdata = realloc(pool->data, newmsize); - if (newdata == NULL) return NULL; - pool->data = newdata; - pool->capacity = newcap; + if (cx_mempool_ensure_capacity(pool, pool->size + 1)) { + return NULL; // LCOV_EXCL_LINE } - struct cx_mempool_memory_s *mem = malloc(sizeof(cx_destructor_func) + n); + struct cx_mempool_memory_s *mem = + cxMalloc(pool->base_allocator, sizeof(struct cx_mempool_memory_s) + n); if (mem == NULL) return NULL; - - mem->destructor = pool->auto_destr; + mem->destructor = NULL; pool->data[pool->size] = mem; pool->size++; return mem->c; } -static void *cx_mempool_calloc( +static void *cx_mempool_calloc_simple( void *p, size_t nelem, size_t elsize @@ -78,53 +103,165 @@ errno = EOVERFLOW; return NULL; } - void *ptr = cx_mempool_malloc(p, msz); + void *ptr = cx_mempool_malloc_simple(p, msz); if (ptr == NULL) return NULL; memset(ptr, 0, nelem * elsize); return ptr; } -static void *cx_mempool_realloc( +static void cx_mempool_free_simple( + void *p, + void *ptr +) { + if (!ptr) return; + struct cx_mempool_s *pool = p; + + struct cx_mempool_memory_s *mem = + (void*) ((char *) ptr - sizeof(struct cx_mempool_memory_s)); + + for (size_t i = 0; i < pool->size; i++) { + if (mem == pool->data[i]) { + if (mem->destructor) { + mem->destructor(mem->c); + } + if (pool->destr) { + pool->destr(mem->c); + } + if (pool->destr2) { + pool->destr2(pool->destr2_data, mem->c); + } + cxFree(pool->base_allocator, mem); + size_t last_index = pool->size - 1; + if (i != last_index) { + pool->data[i] = pool->data[last_index]; + pool->data[last_index] = NULL; + } + pool->size--; + return; + } + } + abort(); // LCOV_EXCL_LINE +} + +static void *cx_mempool_realloc_simple( void *p, void *ptr, size_t n ) { + if (ptr == NULL) { + return cx_mempool_malloc_simple(p, n); + } + if (n == 0) { + cx_mempool_free_simple(p, ptr); + return NULL; + } struct cx_mempool_s *pool = p; - struct cx_mempool_memory_s *mem, *newm; - mem = (struct cx_mempool_memory_s*)(((char *) ptr) - sizeof(cx_destructor_func)); - newm = realloc(mem, n + sizeof(cx_destructor_func)); + const unsigned overhead = sizeof(struct cx_mempool_memory_s); + struct cx_mempool_memory_s *mem = + (void *) (((char *) ptr) - overhead); + struct cx_mempool_memory_s *newm = + cxRealloc(pool->base_allocator, mem, n + overhead); if (newm == NULL) return NULL; if (mem != newm) { for (size_t i = 0; i < pool->size; i++) { if (pool->data[i] == mem) { pool->data[i] = newm; - return ((char*)newm) + sizeof(cx_destructor_func); + return ((char*)newm) + overhead; } } abort(); // LCOV_EXCL_LINE } else { - return ptr; + // unfortunately glibc() realloc seems to always move + return ptr; // LCOV_EXCL_LINE + } +} + +static void cx_mempool_free_all_simple(const struct cx_mempool_s *pool) { + const bool has_destr = pool->destr; + const bool has_destr2 = pool->destr2; + for (size_t i = 0; i < pool->size; i++) { + struct cx_mempool_memory_s *mem = pool->data[i]; + if (mem->destructor) { + mem->destructor(mem->c); + } + if (has_destr) { + pool->destr(mem->c); + } + if (has_destr2) { + pool->destr2(pool->destr2_data, mem->c); + } + cxFree(pool->base_allocator, mem); } } -static void cx_mempool_free( +static cx_allocator_class cx_mempool_simple_allocator_class = { + cx_mempool_malloc_simple, + cx_mempool_realloc_simple, + cx_mempool_calloc_simple, + cx_mempool_free_simple +}; + +static void *cx_mempool_malloc_advanced( + void *p, + size_t n +) { + struct cx_mempool_s *pool = p; + + if (cx_mempool_ensure_capacity(pool, pool->size + 1)) { + return NULL; // LCOV_EXCL_LINE + } + + struct cx_mempool_memory2_s *mem = + cxMalloc(pool->base_allocator, sizeof(struct cx_mempool_memory2_s) + n); + if (mem == NULL) return NULL; + mem->destructor = NULL; + mem->data = NULL; + pool->data[pool->size] = mem; + pool->size++; + + return mem->c; +} + +static void *cx_mempool_calloc_advanced( + void *p, + size_t nelem, + size_t elsize +) { + size_t msz; + if (cx_szmul(nelem, elsize, &msz)) { + errno = EOVERFLOW; + return NULL; + } + void *ptr = cx_mempool_malloc_advanced(p, msz); + if (ptr == NULL) return NULL; + memset(ptr, 0, nelem * elsize); + return ptr; +} + +static void cx_mempool_free_advanced( void *p, void *ptr ) { if (!ptr) return; struct cx_mempool_s *pool = p; - struct cx_mempool_memory_s *mem = (struct cx_mempool_memory_s *) - ((char *) ptr - sizeof(cx_destructor_func)); + struct cx_mempool_memory2_s *mem = + (void*) ((char *) ptr - sizeof(struct cx_mempool_memory2_s)); for (size_t i = 0; i < pool->size; i++) { if (mem == pool->data[i]) { if (mem->destructor) { - mem->destructor(mem->c); + mem->destructor(mem->data, mem->c); + } + if (pool->destr) { + pool->destr(mem->c); } - free(mem); + if (pool->destr2) { + pool->destr2(pool->destr2_data, mem->c); + } + cxFree(pool->base_allocator, mem); size_t last_index = pool->size - 1; if (i != last_index) { pool->data[i] = pool->data[last_index]; @@ -137,19 +274,207 @@ abort(); // LCOV_EXCL_LINE } +static void *cx_mempool_realloc_advanced( + void *p, + void *ptr, + size_t n +) { + if (ptr == NULL) { + return cx_mempool_malloc_advanced(p, n); + } + if (n == 0) { + cx_mempool_free_advanced(p, ptr); + return NULL; + } + struct cx_mempool_s *pool = p; + + const unsigned overhead = sizeof(struct cx_mempool_memory2_s); + struct cx_mempool_memory2_s *mem = + (void *) (((char *) ptr) - overhead); + struct cx_mempool_memory2_s *newm = + cxRealloc(pool->base_allocator, mem, n + overhead); + + if (newm == NULL) return NULL; + if (mem != newm) { + for (size_t i = 0; i < pool->size; i++) { + if (pool->data[i] == mem) { + pool->data[i] = newm; + return ((char*)newm) + overhead; + } + } + abort(); // LCOV_EXCL_LINE + } else { + // unfortunately glibc() realloc seems to always move + return ptr; // LCOV_EXCL_LINE + } +} + +static void cx_mempool_free_all_advanced(const struct cx_mempool_s *pool) { + const bool has_destr = pool->destr; + const bool has_destr2 = pool->destr2; + for (size_t i = 0; i < pool->size; i++) { + struct cx_mempool_memory2_s *mem = pool->data[i]; + if (mem->destructor) { + mem->destructor(mem->data, mem->c); + } + if (has_destr) { + pool->destr(mem->c); + } + if (has_destr2) { + pool->destr2(pool->destr2_data, mem->c); + } + cxFree(pool->base_allocator, mem); + } +} + +static cx_allocator_class cx_mempool_advanced_allocator_class = { + cx_mempool_malloc_advanced, + cx_mempool_realloc_advanced, + cx_mempool_calloc_advanced, + cx_mempool_free_advanced +}; + + +static void *cx_mempool_malloc_pure( + void *p, + size_t n +) { + struct cx_mempool_s *pool = p; + + if (cx_mempool_ensure_capacity(pool, pool->size + 1)) { + return NULL; // LCOV_EXCL_LINE + } + + void *mem = cxMalloc(pool->base_allocator, n); + if (mem == NULL) return NULL; + pool->data[pool->size] = mem; + pool->size++; + + return mem; +} + +static void *cx_mempool_calloc_pure( + void *p, + size_t nelem, + size_t elsize +) { + size_t msz; + if (cx_szmul(nelem, elsize, &msz)) { + errno = EOVERFLOW; + return NULL; + } + void *ptr = cx_mempool_malloc_pure(p, msz); + if (ptr == NULL) return NULL; + memset(ptr, 0, nelem * elsize); + return ptr; +} + +static void cx_mempool_free_pure( + void *p, + void *ptr +) { + if (!ptr) return; + struct cx_mempool_s *pool = p; + + for (size_t i = 0; i < pool->size; i++) { + if (ptr == pool->data[i]) { + if (pool->destr) { + pool->destr(ptr); + } + if (pool->destr2) { + pool->destr2(pool->destr2_data, ptr); + } + cxFree(pool->base_allocator, ptr); + size_t last_index = pool->size - 1; + if (i != last_index) { + pool->data[i] = pool->data[last_index]; + pool->data[last_index] = NULL; + } + pool->size--; + return; + } + } + abort(); // LCOV_EXCL_LINE +} + +static void *cx_mempool_realloc_pure( + void *p, + void *ptr, + size_t n +) { + if (ptr == NULL) { + return cx_mempool_malloc_pure(p, n); + } + if (n == 0) { + cx_mempool_free_pure(p, ptr); + return NULL; + } + struct cx_mempool_s *pool = p; + void *newm = cxRealloc(pool->base_allocator, ptr, n); + if (newm == NULL) return NULL; + if (ptr != newm) { + for (size_t i = 0; i < pool->size; i++) { + if (pool->data[i] == ptr) { + pool->data[i] = newm; + return newm; + } + } + abort(); // LCOV_EXCL_LINE + } else { + // unfortunately glibc() realloc seems to always move + return ptr; // LCOV_EXCL_LINE + } +} + +static void cx_mempool_free_all_pure(const struct cx_mempool_s *pool) { + const bool has_destr = pool->destr; + const bool has_destr2 = pool->destr2; + for (size_t i = 0; i < pool->size; i++) { + void *mem = pool->data[i]; + if (has_destr) { + pool->destr(mem); + } + if (has_destr2) { + pool->destr2(pool->destr2_data, mem); + } + cxFree(pool->base_allocator, mem); + } +} + +static cx_allocator_class cx_mempool_pure_allocator_class = { + cx_mempool_malloc_pure, + cx_mempool_realloc_pure, + cx_mempool_calloc_pure, + cx_mempool_free_pure +}; + +static void cx_mempool_free_foreign(const struct cx_mempool_s *pool) { + for (size_t i = 0; i < pool->registered_size; i++) { + struct cx_mempool_foreign_memory_s info = pool->registered[i]; + if (info.destr2_data == NULL) { + if (info.destr) { + info.destr(info.mem); + } + } else { + info.destr2(info.destr2_data, info.mem); + } + } +} + void cxMempoolFree(CxMempool *pool) { if (pool == NULL) return; - struct cx_mempool_memory_s *mem; - for (size_t i = 0; i < pool->size; i++) { - mem = pool->data[i]; - if (mem->destructor) { - mem->destructor(mem->c); - } - free(mem); + if (pool->allocator->cl == &cx_mempool_simple_allocator_class) { + cx_mempool_free_all_simple(pool); + } else if (pool->allocator->cl == &cx_mempool_advanced_allocator_class) { + cx_mempool_free_all_advanced(pool); + } else { + cx_mempool_free_all_pure(pool); } - free(pool->data); - free((void*) pool->allocator); - free(pool); + cx_mempool_free_foreign(pool); + cxFree(pool->base_allocator, pool->data); + cxFree(pool->base_allocator, pool->registered); + cxFree(pool->base_allocator, (void*) pool->allocator); + cxFree(pool->base_allocator, pool); } void cxMempoolSetDestructor( @@ -159,18 +484,26 @@ *(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = func; } +void cxMempoolSetDestructor2( + void *ptr, + cx_destructor_func2 func, + void *data +) { + struct cx_mempool_memory2_s *info = + (void*)((char *) ptr - sizeof(struct cx_mempool_memory2_s)); + info->destructor = func; + info->data = data; +} + void cxMempoolRemoveDestructor(void *ptr) { *(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = NULL; } -struct cx_mempool_foreign_mem_s { - cx_destructor_func destr; - void* mem; -}; - -static void cx_mempool_destr_foreign_mem(void* ptr) { - struct cx_mempool_foreign_mem_s *fm = ptr; - fm->destr(fm->mem); +void cxMempoolRemoveDestructor2(void *ptr) { + struct cx_mempool_memory2_s *info = + (void*)((char *) ptr - sizeof(struct cx_mempool_memory2_s)); + info->destructor = NULL; + info->data = NULL; } int cxMempoolRegister( @@ -178,60 +511,212 @@ void *memory, cx_destructor_func destr ) { - struct cx_mempool_foreign_mem_s *fm = cx_mempool_malloc( - pool, - sizeof(struct cx_mempool_foreign_mem_s) - ); - if (fm == NULL) return 1; + if (cx_mempool_ensure_registered_capacity(pool, pool->registered_size + 1)) { + return 1; // LCOV_EXCL_LINE + } + + pool->registered[pool->registered_size++] = + (struct cx_mempool_foreign_memory_s) { + .mem = memory, + .destr = destr, + .destr2_data = NULL + }; + + return 0; +} - fm->mem = memory; - fm->destr = destr; - *(cx_destructor_func *) ((char *) fm - sizeof(cx_destructor_func)) = cx_mempool_destr_foreign_mem; +int cxMempoolRegister2( + CxMempool *pool, + void *memory, + cx_destructor_func2 destr, + void *data +) { + if (cx_mempool_ensure_registered_capacity(pool, pool->registered_size + 1)) { + return 1; // LCOV_EXCL_LINE + } + + pool->registered[pool->registered_size++] = + (struct cx_mempool_foreign_memory_s) { + .mem = memory, + .destr2 = destr, + .destr2_data = data + }; return 0; } -static cx_allocator_class cx_mempool_allocator_class = { - cx_mempool_malloc, - cx_mempool_realloc, - cx_mempool_calloc, - cx_mempool_free -}; - CxMempool *cxMempoolCreate( size_t capacity, - cx_destructor_func destr + enum cx_mempool_type type ) { + if (capacity == 0) capacity = 16; size_t poolsize; - if (cx_szmul(capacity, sizeof(struct cx_mempool_memory_s*), &poolsize)) { + if (cx_szmul(capacity, sizeof(void*), &poolsize)) { + // LCOV_EXCL_START errno = EOVERFLOW; return NULL; + } // LCOV_EXCL_STOP + + CxAllocator *provided_allocator = cxMallocDefault(sizeof(CxAllocator)); + if (provided_allocator == NULL) { // LCOV_EXCL_START + return NULL; + } // LCOV_EXCL_STOP + + CxMempool *pool = cxCallocDefault(1, sizeof(CxMempool)); + if (pool == NULL) { // LCOV_EXCL_START + cxFreeDefault(provided_allocator); + return NULL; + } // LCOV_EXCL_STOP + + provided_allocator->data = pool; + *((const CxAllocator**)&pool->base_allocator) = cxDefaultAllocator; + pool->allocator = provided_allocator; + if (type == CX_MEMPOOL_TYPE_SIMPLE) { + provided_allocator->cl = &cx_mempool_simple_allocator_class; + } else if (type == CX_MEMPOOL_TYPE_ADVANCED) { + provided_allocator->cl = &cx_mempool_advanced_allocator_class; + } else { + provided_allocator->cl = &cx_mempool_pure_allocator_class; } - struct cx_mempool_s *pool = - malloc(sizeof(struct cx_mempool_s)); - if (pool == NULL) return NULL; - - CxAllocator *provided_allocator = malloc(sizeof(CxAllocator)); - if (provided_allocator == NULL) { // LCOV_EXCL_START - free(pool); - return NULL; - } // LCOV_EXCL_STOP - provided_allocator->cl = &cx_mempool_allocator_class; - provided_allocator->data = pool; - - pool->allocator = provided_allocator; - - pool->data = malloc(poolsize); + pool->data = cxMallocDefault(poolsize); if (pool->data == NULL) { // LCOV_EXCL_START - free(provided_allocator); - free(pool); + cxFreeDefault(provided_allocator); + cxFreeDefault(pool); return NULL; } // LCOV_EXCL_STOP pool->size = 0; pool->capacity = capacity; - pool->auto_destr = destr; return pool; } + +void cxMempoolGlobalDestructor(CxMempool *pool, cx_destructor_func fnc) { + pool->destr = fnc; +} + +void cxMempoolGlobalDestructor2(CxMempool *pool, cx_destructor_func2 fnc, void *data) { + pool->destr2 = fnc; + pool->destr2_data = data; +} + +static void cx_mempool_free_transferred_allocator(void *base_al, void *al) { + cxFree(base_al, al); +} + +int cxMempoolTransfer( + CxMempool *source, + CxMempool *dest +) { + // safety checks + if (source == dest) return 1; + if (source->allocator->cl != dest->allocator->cl) return 1; + if (source->base_allocator->cl != dest->base_allocator->cl) return 1; + + // ensure enough capacity in the destination pool + if (cx_mempool_ensure_capacity(dest, dest->size + source->size)) { + return 1; // LCOV_EXCL_LINE + } + if (cx_mempool_ensure_registered_capacity(dest, + dest->registered_size + source->registered_size)) { + return 1; // LCOV_EXCL_LINE + } + + // allocate a replacement allocator for the source pool + CxAllocator *new_source_allocator = + cxMalloc(source->base_allocator, sizeof(CxAllocator)); + if (new_source_allocator == NULL) { // LCOV_EXCL_START + return 1; + } // LCOV_EXCL_STOP + new_source_allocator->cl = source->allocator->cl; + new_source_allocator->data = source; + + // transfer all the data + if (source->size > 0) { + memcpy(&dest->data[dest->size], source->data, + sizeof(void*)*source->size); + dest->size += source->size; + } + + // transfer all registered memory + if (source->registered_size > 0) { + memcpy(&dest->registered[dest->registered_size], source->registered, + sizeof(struct cx_mempool_foreign_memory_s) + * source->registered_size); + dest->registered_size += source->registered_size; + } + + // register the old allocator with the new pool + // we have to remove const-ness for this, but that's okay here + // also register the base allocator, s.t. the pool knows how to free it + CxAllocator *transferred_allocator = (CxAllocator*) source->allocator; + transferred_allocator->data = dest; + cxMempoolRegister2(dest, transferred_allocator, + cx_mempool_free_transferred_allocator, (void*)source->base_allocator); + + // prepare the source pool for re-use + source->allocator = new_source_allocator; + memset(source->data, 0, source->size * sizeof(void*)); + memset(source->registered, 0, + source->registered_size * sizeof(struct cx_mempool_foreign_memory_s)); + source->size = 0; + source->registered_size = 0; + + return 0; +} + +int cxMempoolTransferObject( + CxMempool *source, + CxMempool *dest, + const void *obj +) { + // safety checks + if (source == dest) return 1; + if (source->allocator->cl != dest->allocator->cl) return 1; + if (source->base_allocator->cl != dest->base_allocator->cl) return 1; + + // search for the object + for (size_t i = 0; i < source->size; i++) { + struct cx_mempool_memory_s *mem = source->data[i]; + if (mem->c == obj) { + // first, make sure that the dest pool can take the object + if (cx_mempool_ensure_capacity(dest, dest->size + 1)) { + return 1; // LCOV_EXCL_LINE + } + // remove from the source pool + size_t last_index = source->size - 1; + if (i != last_index) { + source->data[i] = source->data[last_index]; + source->data[last_index] = NULL; + } + source->size--; + // add to the target pool + dest->data[dest->size++] = mem; + return 0; + } + } + // search in the registered objects + for (size_t i = 0; i < source->registered_size; i++) { + struct cx_mempool_foreign_memory_s *mem = &source->registered[i]; + if (mem->mem == obj) { + // first, make sure that the dest pool can take the object + if (cx_mempool_ensure_registered_capacity(dest, + dest->registered_size + 1)) { + return 1; // LCOV_EXCL_LINE + } + dest->registered[dest->registered_size++] = *mem; + // remove from the source pool + size_t last_index = source->registered_size - 1; + if (i != last_index) { + source->registered[i] = source->registered[last_index]; + memset(&source->registered[last_index], 0, + sizeof(struct cx_mempool_foreign_memory_s)); + } + source->registered_size--; + return 0; + } + } + // not found + return 1; +}
--- a/ucx/printf.c Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/printf.c Tue Sep 09 20:56:47 2025 +0200 @@ -68,7 +68,7 @@ return (int) wfc(buf, 1, ret, stream); } else { int len = ret + 1; - char *newbuf = malloc(len); + char *newbuf = cxMallocDefault(len); if (!newbuf) { // LCOV_EXCL_START va_end(ap2); return -1; @@ -79,7 +79,7 @@ if (ret > 0) { ret = (int) wfc(newbuf, 1, ret, stream); } - free(newbuf); + cxFreeDefault(newbuf); } return ret; } @@ -121,7 +121,7 @@ if (s.ptr) { ret = vsnprintf(s.ptr, len, fmt, ap2); if (ret < 0) { - free(s.ptr); + cxFree(a, s.ptr); s.ptr = NULL; } else { s.length = (size_t) ret; @@ -132,7 +132,13 @@ return s; } -int cx_sprintf_a(CxAllocator *alloc, char **str, size_t *len, const char *fmt, ... ) { +int cx_sprintf_a( + const CxAllocator *alloc, + char **str, + size_t *len, + const char *fmt, + ... +) { va_list ap; va_start(ap, fmt); int ret = cx_vsprintf_a(alloc, str, len, fmt, ap); @@ -140,7 +146,13 @@ return ret; } -int cx_vsprintf_a(CxAllocator *alloc, char **str, size_t *len, const char *fmt, va_list ap) { +int cx_vsprintf_a( + const CxAllocator *alloc, + char **str, + size_t *len, + const char *fmt, + va_list ap +) { va_list ap2; va_copy(ap2, ap); int ret = vsnprintf(*str, *len, fmt, ap); @@ -162,7 +174,14 @@ return ret; } -int cx_sprintf_sa(CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, ... ) { +int cx_sprintf_sa( + const CxAllocator *alloc, + char *buf, + size_t *len, + char **str, + const char *fmt, + ... +) { va_list ap; va_start(ap, fmt); int ret = cx_vsprintf_sa(alloc, buf, len, str, fmt, ap); @@ -170,7 +189,14 @@ return ret; } -int cx_vsprintf_sa(CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, va_list ap) { +int cx_vsprintf_sa( + const CxAllocator *alloc, + char *buf, + size_t *len, + char **str, + const char *fmt, + va_list ap +) { va_list ap2; va_copy(ap2, ap); int ret = vsnprintf(buf, *len, fmt, ap);
--- a/ucx/properties.c Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/properties.c Tue Sep 09 20:56:47 2025 +0200 @@ -287,7 +287,7 @@ cx_attr_unused CxProperties *prop, CxPropertiesSource *src ) { - src->data_ptr = malloc(src->data_size); + src->data_ptr = cxMallocDefault(src->data_size); if (src->data_ptr == NULL) return 1; return 0; } @@ -296,7 +296,7 @@ cx_attr_unused CxProperties *prop, CxPropertiesSource *src ) { - free(src->data_ptr); + cxFreeDefault(src->data_ptr); } CxPropertiesSource cxPropertiesStringSource(cxstring str) {
--- a/ucx/streams.c Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/streams.c Tue Sep 09 20:56:47 2025 +0200 @@ -27,6 +27,7 @@ */ #include "cx/streams.h" +#include "cx/allocator.h" #ifndef CX_STREAM_BCOPY_BUF_SIZE #define CX_STREAM_BCOPY_BUF_SIZE 8192 @@ -57,7 +58,7 @@ lbuf = buf; } else { if (bufsize == 0) bufsize = CX_STREAM_BCOPY_BUF_SIZE; - lbuf = malloc(bufsize); + lbuf = cxMallocDefault(bufsize); if (lbuf == NULL) return 0; } @@ -74,7 +75,7 @@ } if (lbuf != buf) { - free(lbuf); + cxFreeDefault(lbuf); } return ncp;
--- a/ucx/string.c Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/string.c Tue Sep 09 20:56:47 2025 +0200 @@ -42,7 +42,7 @@ #endif cxmutstr cx_mutstr(char *cstring) { - return (cxmutstr) {cstring, strlen(cstring)}; + return (cxmutstr) {cstring, cstring == NULL ? 0 : strlen(cstring)}; } cxmutstr cx_mutstrn( @@ -53,7 +53,7 @@ } cxstring cx_str(const char *cstring) { - return (cxstring) {cstring, strlen(cstring)}; + return (cxstring) {cstring, cstring == NULL ? 0 : strlen(cstring)}; } cxstring cx_strn( @@ -65,7 +65,7 @@ void cx_strfree(cxmutstr *str) { if (str == NULL) return; - free(str->ptr); + cxFreeDefault(str->ptr); str->ptr = NULL; str->length = 0; } @@ -80,6 +80,22 @@ str->length = 0; } +int cx_strcpy_a( + const CxAllocator *alloc, + cxmutstr *dest, + cxstring src +) { + if (cxReallocate(alloc, &dest->ptr, src.length + 1)) { + return 1; + } + + memcpy(dest->ptr, src.ptr, src.length); + dest->length = src.length; + dest->ptr[dest->length] = '\0'; + + return 0; +} + size_t cx_strlen( size_t count, ... @@ -106,27 +122,16 @@ ... ) { if (count == 0) return str; - - cxstring strings_stack[8]; - cxstring *strings; - if (count > 8) { - strings = calloc(count, sizeof(cxstring)); - if (strings == NULL) { - return (cxmutstr) {NULL, 0}; - } - } else { - strings = strings_stack; - } - va_list ap; va_start(ap, count); + va_list ap2; + va_copy(ap2, ap); - // get all args and overall length + // compute overall length bool overflow = false; size_t slen = str.length; for (size_t i = 0; i < count; i++) { - cxstring s = va_arg (ap, cxstring); - strings[i] = s; + cxstring s = va_arg(ap, cxstring); if (slen > SIZE_MAX - str.length) overflow = true; slen += s.length; } @@ -134,10 +139,8 @@ // abort in case of overflow if (overflow) { + va_end(ap2); errno = EOVERFLOW; - if (strings != strings_stack) { - free(strings); - } return (cxmutstr) { NULL, 0 }; } @@ -149,9 +152,7 @@ newstr = cxRealloc(alloc, str.ptr, slen + 1); } if (newstr == NULL) { - if (strings != strings_stack) { - free(strings); - } + va_end(ap2); return (cxmutstr) {NULL, 0}; } str.ptr = newstr; @@ -160,19 +161,15 @@ size_t pos = str.length; str.length = slen; for (size_t i = 0; i < count; i++) { - cxstring s = strings[i]; + cxstring s = va_arg(ap2, cxstring); memcpy(str.ptr + pos, s.ptr, s.length); pos += s.length; } + va_end(ap2); // terminate string str.ptr[str.length] = '\0'; - // free temporary array - if (strings != strings_stack) { - free(strings); - } - return str; } @@ -289,8 +286,9 @@ // check needle length and use appropriate prefix table // if the pattern exceeds static prefix table, allocate on the heap const bool useheap = needle.length >= CX_STRSTR_SBO_SIZE; - register size_t *ptable = useheap ? calloc(needle.length + 1, - sizeof(size_t)) : s_prefix_table; + register size_t *ptable = useheap + ? cxCallocDefault(needle.length + 1, sizeof(size_t)) + : s_prefix_table; // keep counter in registers register size_t i, j; @@ -328,7 +326,7 @@ // if prefix table was allocated on the heap, free it if (useheap) { - free(ptable); + cxFreeDefault(ptable); } return result; @@ -588,27 +586,6 @@ #endif } -#ifndef CX_STRREPLACE_INDEX_BUFFER_SIZE -#define CX_STRREPLACE_INDEX_BUFFER_SIZE 64 -#endif - -struct cx_strreplace_ibuf { - size_t *buf; - struct cx_strreplace_ibuf *next; - unsigned int len; -}; - -static void cx_strrepl_free_ibuf(struct cx_strreplace_ibuf *buf) { - // remember, the first data is on the stack! - buf = buf->next; - while (buf) { - struct cx_strreplace_ibuf *next = buf->next; - free(buf->buf); - free(buf); - buf = next; - } -} - cxmutstr cx_strreplacen_a( const CxAllocator *allocator, cxstring str, @@ -616,108 +593,60 @@ cxstring replacement, size_t replmax ) { + // special cases + if (search.length == 0 || search.length > str.length || replmax == 0) { + return cx_strdup_a(allocator, str); + } - if (search.length == 0 || search.length > str.length || replmax == 0) - return cx_strdup_a(allocator, str); + size_t in_len = str.length; + size_t search_len = search.length; + size_t repl_len = replacement.length; - // Compute expected buffer length - size_t ibufmax = str.length / search.length; - size_t ibuflen = replmax < ibufmax ? replmax : ibufmax; - if (ibuflen > CX_STRREPLACE_INDEX_BUFFER_SIZE) { - ibuflen = CX_STRREPLACE_INDEX_BUFFER_SIZE; + // first run, count the occurrences + // and remember where the first is + size_t occurrences = 1; + cxstring first = cx_strstr(str, search); + if (first.length == 0) { + // special case, no replacements + return cx_strdup_a(allocator, str); + } + cxstring tmp = cx_strsubs(first, search_len); + while (occurrences < replmax && + (tmp = cx_strstr(tmp, search)).length > 0) { + occurrences++; + tmp = cx_strsubs(tmp, search_len); } - // First index buffer can be on the stack - struct cx_strreplace_ibuf ibuf, *curbuf = &ibuf; - size_t ibuf_sbo[CX_STRREPLACE_INDEX_BUFFER_SIZE]; - ibuf.buf = ibuf_sbo; - ibuf.next = NULL; - ibuf.len = 0; + // calculate necessary memory + signed long long diff_len = (signed long long) repl_len - search_len; + size_t out_len = in_len + diff_len * occurrences; + cxmutstr out = { + cxMalloc(allocator, out_len + 1), + out_len + }; + if (out.ptr == NULL) return out; - // Search occurrences - cxstring searchstr = str; - size_t found = 0; - do { - cxstring match = cx_strstr(searchstr, search); - if (match.length > 0) { - // Allocate next buffer in chain, if required - if (curbuf->len == ibuflen) { - struct cx_strreplace_ibuf *nextbuf = - calloc(1, sizeof(struct cx_strreplace_ibuf)); - if (!nextbuf) { - cx_strrepl_free_ibuf(&ibuf); - return cx_mutstrn(NULL, 0); - } - nextbuf->buf = calloc(ibuflen, sizeof(size_t)); - if (!nextbuf->buf) { - free(nextbuf); - cx_strrepl_free_ibuf(&ibuf); - return cx_mutstrn(NULL, 0); - } - curbuf->next = nextbuf; - curbuf = nextbuf; - } - - // Record match index - found++; - size_t idx = match.ptr - str.ptr; - curbuf->buf[curbuf->len++] = idx; - searchstr.ptr = match.ptr + search.length; - searchstr.length = str.length - idx - search.length; - } else { - break; - } - } while (searchstr.length > 0 && found < replmax); - - // Allocate result string - cxmutstr result; - { - long long adjlen = (long long) replacement.length - (long long) search.length; - size_t rcount = 0; - curbuf = &ibuf; - do { - rcount += curbuf->len; - curbuf = curbuf->next; - } while (curbuf); - result.length = str.length + rcount * adjlen; - result.ptr = cxMalloc(allocator, result.length + 1); - if (!result.ptr) { - cx_strrepl_free_ibuf(&ibuf); - return cx_mutstrn(NULL, 0); - } + // second run: perform the replacements + // but start where we found the first occurrence + const char *inp = str.ptr; + tmp = first; + char *outp = out.ptr; + while (occurrences-- > 0 && (tmp = cx_strstr(tmp, search)).length > 0) { + size_t copylen = tmp.ptr - inp; + memcpy(outp, inp, copylen); + outp += copylen; + memcpy(outp, replacement.ptr, repl_len); + outp += repl_len; + inp += copylen + search_len; + tmp = cx_strsubs(tmp, search_len); } - // Build result string - curbuf = &ibuf; - size_t srcidx = 0; - char *destptr = result.ptr; - do { - for (size_t i = 0; i < curbuf->len; i++) { - // Copy source part up to next match - size_t idx = curbuf->buf[i]; - size_t srclen = idx - srcidx; - if (srclen > 0) { - memcpy(destptr, str.ptr + srcidx, srclen); - destptr += srclen; - srcidx += srclen; - } + // add the remaining string + size_t copylen = in_len - (inp - str.ptr); + memcpy(outp, inp, copylen); + out.ptr[out_len] = '\0'; - // Copy the replacement and skip the source pattern - srcidx += search.length; - memcpy(destptr, replacement.ptr, replacement.length); - destptr += replacement.length; - } - curbuf = curbuf->next; - } while (curbuf); - memcpy(destptr, str.ptr + srcidx, str.length - srcidx); - - // Result is guaranteed to be zero-terminated - result.ptr[result.length] = '\0'; - - // Free index buffer - cx_strrepl_free_ibuf(&ibuf); - - return result; + return out; } CxStrtokCtx cx_strtok_(
--- a/ucx/tree.c Tue Sep 09 16:01:30 2025 +0200 +++ b/ucx/tree.c Tue Sep 09 20:56:47 2025 +0200 @@ -226,14 +226,14 @@ int ret_elem = sfunc(elem, node); if (ret_elem == 0) { // if found, exit the search - *result = (void *) elem; + *result = elem; ret = 0; break; } else if (ret_elem > 0 && ret_elem < ret) { // new distance is better *result = elem; ret = ret_elem; - } else { + } else if (ret_elem < 0 || ret_elem > ret) { // not contained or distance is worse, skip entire subtree cxTreeIteratorContinue(iter); } @@ -305,12 +305,12 @@ if (children == NULL) { // search for the next node - void *next; + void *next = NULL; cx_tree_iter_search_next: - // check if there is a sibling + // check if there is a sibling, but only if we are not a (subtree-)root if (iter->exiting) { next = iter->node_next; - } else { + } else if (iter->depth > 1) { next = tree_next(iter->node); iter->node_next = next; } @@ -326,7 +326,7 @@ // invalidate the iterator and free the node stack iter->node = iter->node_next = NULL; iter->stack_capacity = iter->depth = 0; - free(iter->stack); + cxFreeDefault(iter->stack); iter->stack = NULL; } else { // the parent node can be obtained from the top of stack @@ -386,7 +386,7 @@ iter.node = root; if (root != NULL) { iter.stack_capacity = 16; - iter.stack = malloc(sizeof(void *) * 16); + iter.stack = cxMallocDefault(sizeof(void *) * 16); iter.stack[0] = root; iter.counter = 1; iter.depth = 1; @@ -416,7 +416,7 @@ node = tree_next(node); while (node != NULL) { struct cx_tree_visitor_queue_s *q; - q = malloc(sizeof(struct cx_tree_visitor_queue_s)); + q = cxMallocDefault(sizeof(struct cx_tree_visitor_queue_s)); q->depth = iter->queue_last->depth; q->node = node; iter->queue_last->next = q; @@ -445,7 +445,7 @@ } if (children != NULL) { struct cx_tree_visitor_queue_s *q; - q = malloc(sizeof(struct cx_tree_visitor_queue_s)); + q = cxMallocDefault(sizeof(struct cx_tree_visitor_queue_s)); q->depth = iter->depth + 1; q->node = children; if (iter->queue_last == NULL) { @@ -474,7 +474,7 @@ assert(iter->queue_last == q); iter->queue_last = NULL; } - free(q); + cxFreeDefault(q); } // increment the node counter