Thu, 15 May 2025 16:02:54 +0200
allow changing the cxDefaultAllocator - resolves #669
--- a/CHANGELOG Thu May 15 15:43:30 2025 +0200 +++ b/CHANGELOG Thu May 15 16:02:54 2025 +0200 @@ -12,6 +12,7 @@ * adds cxTreeSize() * adds CX_PRIstr and CX_SFMT macros for formatting UCX strings * adds cx_strcpy() and cx_strcpy_a() + * adds cxStdlibAllocator and allows changes of cxDefaultAllocator * improves performance of the CxList array list implementation * changes grow strategy for the mempory pool to reduce reallocations * changes grow strategy for CxBuffer, which does now take the page size into account @@ -24,6 +25,7 @@ * fixes implementation of cxBufferTerminate() * fixes allocator arguments for some printf.h functions not being const * fixes that cx_tree_search() did not investigate subtrees with equally good distance + * fixes that memory was freed by the wrong allocator in cx_vasprintf_a() when the underlying vsnprintf() failed * removes the use of C23 attributes because they don't mix well with GNU attributes in GCC 15 Version 3.1 - 2025-02-11
--- a/docs/Writerside/topics/about.md Thu May 15 15:43:30 2025 +0200 +++ b/docs/Writerside/topics/about.md Thu May 15 16:02:54 2025 +0200 @@ -39,6 +39,7 @@ * adds cxTreeSize() * adds CX_PRIstr and CX_SFMT macros for formatting UCX strings * adds cx_strcpy() and cx_strcpy_a() +* adds cxStdlibAllocator and allows changes of cxDefaultAllocator * improves performance of the CxList array list implementation * changes grow strategy for the memory pool to reduce reallocations * changes grow strategy for CxBuffer, which does now take the page size into account @@ -51,6 +52,7 @@ * fixes implementation of cxBufferTerminate() * fixes allocator arguments for some printf.h functions not being const * fixes that cx_tree_search() did not investigate subtrees with equally good distance +* fixes that memory was freed by the wrong allocator in cx_vasprintf_a() when the underlying vsnprintf() failed * removes the use of C23 attributes because they don't mix well with GNU attributes in GCC 15 ### Version 3.1 - 2025-02-11 {collapsible="true"}
--- a/docs/Writerside/topics/allocator.h.md Thu May 15 15:43:30 2025 +0200 +++ b/docs/Writerside/topics/allocator.h.md Thu May 15 16:02:54 2025 +0200 @@ -4,11 +4,18 @@ that can also be used in many other function in UCX. A default allocator implementation using the stdlib functions is -available via the global symbol `cxDefaultAllocator` +available via the global symbol `cxStdlibAllocator`, and UCX also provides a [memory pool](mempool.h.md) implementation. -You are free to add additional own custom implementations. +You are free to add your additional, own custom implementations. A general sketch that illustrates how to do this can be found [below](#custom-allocator). +## Default Allocator + +The global default allocator which is used by UCX, +when no specific allocator is specified, +can be configured via the `cxDefaultAllocator`. +It is by default set to the `cxStdlibAllocator`. + ## Overview ```C @@ -36,12 +43,19 @@ int cx_reallocatearray(void **mem, size_t nmemb, size_t size); // predefined allocator that uses stdlib functions -CxAllocator *cxDefaultAllocator; +CxAllocator * const cxStdlibAllocator; + +// default allocator that can be changed +CxAllocator *cxDefaultAllocator = cxStdlibAllocator; ``` > All UCX functions that are not _explicitly_ designed for taking an allocator argument > (recognizable by a `_a` suffix in the function's name) do support a `NULL` argument > in which case the `cxDefaultAllocator` will be used. +> +> You may change the default allocator at any time, but it is strongly recommended to +> do it only once at program start to avoid accidentally freeing memory that was +> allocated by a different allocator. ## Description
--- a/docs/Writerside/topics/array_list.h.md Thu May 15 15:43:30 2025 +0200 +++ b/docs/Writerside/topics/array_list.h.md Thu May 15 16:02:54 2025 +0200 @@ -66,7 +66,7 @@ > saves eight bytes (assuming proper alignment in your struct). Once the array is declared, you can use one of the macros `cx_array_initialize()` or `cx_array_initialize_a()` to allocate memory. -The former uses a stdlib default allocator and the latter allows you to use a specific allocator. +The former uses the [default allocator](allocator.h.md#default-allocator) and the latter allows you to use a specific allocator. Important to note is, that the `ARRAY` argument expects the variable's _name_. The macros set `ARRAY_size` to zero, `ARRAY_capacity` to the specified initial capacity, and invoke the allocator's `malloc()` function to allocate the memory. @@ -114,7 +114,7 @@ This is realized by passing a reallocator to the various functions which specifies how an array shall be reallocated when needed. -The default `cx_array_default_reallocator` simply defers to the standard library `realloc()`. +The default `cx_array_default_reallocator` simply defers to the [default allocator](allocator.h.md#default-allocator). A reallocator created with the `cx_array_reallocator()` function uses a more sophisticated approach. On the one hand, it can use an arbitrary UCX [allocator](allocator.h.md) for the reallocation,
--- a/docs/Writerside/topics/compare.h.md Thu May 15 15:43:30 2025 +0200 +++ b/docs/Writerside/topics/compare.h.md Thu May 15 16:02:54 2025 +0200 @@ -12,7 +12,7 @@ ```C CxList *list = cxArrayListCreate( - cxDefaultAllocator, // use the default stdlib allocator + cxDefaultAllocator, // use the default allocator cx_cmp_int32, // the compare function for the elements sizeof(int32_t), // the size of one element 256 // reseve space for 256 elements
--- a/docs/Writerside/topics/hash_map.h.md Thu May 15 15:43:30 2025 +0200 +++ b/docs/Writerside/topics/hash_map.h.md Thu May 15 16:02:54 2025 +0200 @@ -21,7 +21,7 @@ The function `cxHashMapCreate()` creates a new [map](map.h.md) where both the map structure and the contained buckets are allocated by the specified `allocator`. -The default stdlib allocator is used in `cxHashMapCreateSimple()`. +The [default allocator](allocator.h.md#default-allocator) is used in `cxHashMapCreateSimple()`. The map will store items of size `itemsize`. You can use the `CX_STORE_POINTERS` macro for `itemsize` to indicate that the map shall store
--- a/docs/Writerside/topics/list.h.md Thu May 15 15:43:30 2025 +0200 +++ b/docs/Writerside/topics/list.h.md Thu May 15 16:02:54 2025 +0200 @@ -26,7 +26,7 @@ The function `cxLinkedListCreate()` creates a new linked list with the specified `allocator` which stores elements of size `elem_size`. The elements are supposed to be compared with the specified `comparator` (cf. [](#access-and-find)). -The function `cxLinkedListCreateSimple()` will use the stdlib default allocator and does not specify a compare function. +The function `cxLinkedListCreateSimple()` will use the [default allocator](allocator.h.md#default-allocator) and does not specify a compare function. Array lists can be created with `cxArrayListCreate()` where the additional argument `initial_capacity` specifies for how many elements the underlying array shall be initially allocated.
--- a/docs/Writerside/topics/map.h.md Thu May 15 15:43:30 2025 +0200 +++ b/docs/Writerside/topics/map.h.md Thu May 15 16:02:54 2025 +0200 @@ -15,7 +15,7 @@ The function `cxHashMapCreate()` creates a new map where both the map structure and the contained buckets are allocated by the specified `allocator`. -The default stdlib allocator is used in `cxHashMapCreateSimple()`. +The [default allocator](allocator.h.md#default-allocator) is used in `cxHashMapCreateSimple()`. The map will store items of size `itemsize`. You can use the `CX_STORE_POINTERS` macro for `itemsize` to indicate that the map shall store
--- a/docs/Writerside/topics/memory.md Thu May 15 15:43:30 2025 +0200 +++ b/docs/Writerside/topics/memory.md Thu May 15 16:02:54 2025 +0200 @@ -4,7 +4,7 @@ Many UCX functions support the use of specialized allocators or provide a second function suffixed with `_a`. For convenience, functions that are not explicitly requesting an allocator - like e.g. `cx_strdup_a()` - also accept -`NULL` as an allocator, in which case the `cxDefaultAllocator` and stdlib functions are used. +`NULL` as an allocator, in which case the [default allocator](allocator.h.md#default-allocator) is used. Additionally, UCX also provides a [memory pool](mempool.h.md) implementation of the allocator interface.
--- a/docs/Writerside/topics/string.h.md Thu May 15 15:43:30 2025 +0200 +++ b/docs/Writerside/topics/string.h.md Thu May 15 16:02:54 2025 +0200 @@ -59,7 +59,7 @@ The function `cx_strdup_a()` allocates new memory with the given `allocator` and copies the given `string` and guarantees that the result string is zero-terminated. -The function `cx_strdup()` is equivalent to `cx_strdup_a()`, except that it uses the default stdlib allocator. +The function `cx_strdup()` is equivalent to `cx_strdup_a()`, except that it uses the [default allocator](allocator.h.md#default-allocator). The functions `cx_strcpy_a()` and `cx_strcpy()` copy the contents of the `source` string to the `dest` string, and also guarantee zero-termination of the resulting string. @@ -132,7 +132,7 @@ The `cx_strcat_a()` function takes `count` UCX strings, allocates memory for a concatenation of those strings _with a single allocation_, and copies the contents of the strings to the new memory. -`cx_strcat()` is equivalent, except that is uses the default stdlib allocator. +`cx_strcat()` is equivalent, except that it uses the [default allocator](allocator.h.md#default-allocator). The `cx_strcat_ma()` and `cx_strcat_m()` append the `count` strings to the specified string `str` and, instead of allocating new memory, reallocate the existing memory in `str`.
--- a/src/allocator.c Thu May 15 15:43:30 2025 +0200 +++ b/src/allocator.c Thu May 15 16:02:54 2025 +0200 @@ -60,18 +60,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,
--- a/src/array_list.c Thu May 15 15:43:30 2025 +0200 +++ b/src/array_list.c Thu May 15 16:02:54 2025 +0200 @@ -45,7 +45,7 @@ errno = EOVERFLOW; return NULL; } - return realloc(array, n); + return cxRealloc(cxDefaultAllocator, array, n); } CxArrayReallocator cx_array_default_reallocator_impl = { @@ -572,7 +572,7 @@ // decide if we can use the local buffer if (elem_size > CX_ARRAY_SWAP_SBO_SIZE) { - tmp = malloc(elem_size); + tmp = cxMalloc(cxDefaultAllocator, elem_size); // we don't want to enforce error handling if (tmp == NULL) abort(); } else { @@ -591,7 +591,7 @@ // free dynamic memory, if it was needed if (tmp != sbo_mem) { - free(tmp); + cxFree(cxDefaultAllocator, tmp); } }
--- a/src/buffer.c Thu May 15 15:43:30 2025 +0200 +++ b/src/buffer.c Thu May 15 16:02:54 2025 +0200 @@ -98,7 +98,7 @@ CxBuffer *buffer, CxBufferFlushConfig config ) { - buffer->flush = malloc(sizeof(CxBufferFlushConfig)); + buffer->flush = cxMalloc(cxDefaultAllocator, sizeof(CxBufferFlushConfig)); if (buffer->flush == NULL) return -1; // LCOV_EXCL_LINE memcpy(buffer->flush, &config, sizeof(CxBufferFlushConfig)); return 0; @@ -108,7 +108,7 @@ if (buffer->flags & CX_BUFFER_FREE_CONTENTS) { cxFree(buffer->allocator, buffer->bytes); } - free(buffer->flush); + cxFree(cxDefaultAllocator, buffer->flush); memset(buffer, 0, sizeof(CxBuffer)); }
--- a/src/cx/allocator.h Thu May 15 15:43:30 2025 +0200 +++ b/src/cx/allocator.h Thu May 15 16:02:54 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.
--- a/src/cx/array_list.h Thu May 15 15:43:30 2025 +0200 +++ b/src/cx/array_list.h Thu May 15 16:02:54 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 = cxMalloc(cxDefaultAllocator, 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 @@ -217,7 +217,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 +225,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 +699,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)
--- a/src/cx/buffer.h Thu May 15 15:43:30 2025 +0200 +++ b/src/cx/buffer.h Thu May 15 16:02:54 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 */
--- a/src/cx/hash_map.h Thu May 15 15:43:30 2025 +0200 +++ b/src/cx/hash_map.h Thu May 15 16:02:54 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/src/cx/linked_list.h Thu May 15 15:43:30 2025 +0200 +++ b/src/cx/linked_list.h Thu May 15 16:02:54 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)
--- a/src/cx/properties.h Thu May 15 15:43:30 2025 +0200 +++ b/src/cx/properties.h Thu May 15 16:02:54 2025 +0200 @@ -555,7 +555,7 @@ * zero-terminated C strings (@c char*), which means the @p map should have been * created with #CX_STORE_POINTERS. * - * The default stdlib allocator will be used, unless you specify a custom + * 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.
--- a/src/cx/string.h Thu May 15 15:43:30 2025 +0200 +++ b/src/cx/string.h Thu May 15 16:02:54 2025 +0200 @@ -305,7 +305,7 @@ #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. @@ -454,7 +454,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 @@ -474,7 +474,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 @@ -930,8 +930,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. * @@ -1062,7 +1062,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, @@ -1098,7 +1098,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/src/cx/tree.h Thu May 15 15:43:30 2025 +0200 +++ b/src/cx/tree.h Thu May 15 16:02:54 2025 +0200 @@ -213,7 +213,7 @@ */ cx_attr_nonnull static inline void cxTreeIteratorDispose(CxTreeIterator *iter) { - free(iter->stack); + cxFree(cxDefaultAllocator, iter->stack); iter->stack = NULL; } @@ -226,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); + cxFree(cxDefaultAllocator, q); q = next; } } @@ -443,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 @@ -471,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 @@ -958,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 @@ -1022,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
--- a/src/json.c Thu May 15 15:43:30 2025 +0200 +++ b/src/json.c Thu May 15 16:02:54 2025 +0200 @@ -500,7 +500,7 @@ if (all_printable && escape) { size_t capa = str.length + 32; - char *space = malloc(capa); + char *space = cxMalloc(cxDefaultAllocator, 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 +631,10 @@ void cxJsonDestroy(CxJson *json) { cxBufferDestroy(&json->buffer); if (json->states != json->states_internal) { - free(json->states); + cxFree(cxDefaultAllocator, json->states); } if (json->vbuf != json->vbuf_internal) { - free(json->vbuf); + cxFree(cxDefaultAllocator, json->vbuf); } cxJsonValueFree(json->parsed); json->parsed = NULL; @@ -984,67 +984,67 @@ if (values[i] == NULL) break; cxJsonValueFree(values[i]); } - free(values); + cxFree(cxDefaultAllocator, values); } // LCOV_EXCL_STOP int cxJsonArrAddNumbers(CxJsonValue* arr, const double* num, size_t count) { - CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); + CxJsonValue** values = cxCalloc(cxDefaultAllocator, 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); + cxFree(cxDefaultAllocator, values); return ret; } int cxJsonArrAddIntegers(CxJsonValue* arr, const int64_t* num, size_t count) { - CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); + CxJsonValue** values = cxCalloc(cxDefaultAllocator, 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); + cxFree(cxDefaultAllocator, values); return ret; } int cxJsonArrAddStrings(CxJsonValue* arr, const char* const* str, size_t count) { - CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); + CxJsonValue** values = cxCalloc(cxDefaultAllocator, 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); + cxFree(cxDefaultAllocator, values); return ret; } int cxJsonArrAddCxStrings(CxJsonValue* arr, const cxstring* str, size_t count) { - CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); + CxJsonValue** values = cxCalloc(cxDefaultAllocator, 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); + cxFree(cxDefaultAllocator, values); return ret; } int cxJsonArrAddLiterals(CxJsonValue* arr, const CxJsonLiteral* lit, size_t count) { - CxJsonValue** values = calloc(count, sizeof(CxJsonValue*)); + CxJsonValue** values = cxCalloc(cxDefaultAllocator, 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); + cxFree(cxDefaultAllocator, values); return ret; }
--- a/src/linked_list.c Thu May 15 15:43:30 2025 +0200 +++ b/src/linked_list.c Thu May 15 16:02:54 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; + cxMalloc(cxDefaultAllocator, 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); + cxFree(cxDefaultAllocator, sorted); } }
--- a/src/list.c Thu May 15 15:43:30 2025 +0200 +++ b/src/list.c Thu May 15 16:02:54 2025 +0200 @@ -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 = cxMalloc(cxDefaultAllocator, elem_size * list_size); if (tmp == NULL) abort(); // copy elements from source array @@ -377,7 +377,7 @@ loc += elem_size; } - free(tmp); + cxFree(cxDefaultAllocator, 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 = cxMalloc(cxDefaultAllocator, 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); + cxFree(cxDefaultAllocator, tmp); return 0; }
--- a/src/mempool.c Thu May 15 15:43:30 2025 +0200 +++ b/src/mempool.c Thu May 15 16:02:54 2025 +0200 @@ -51,7 +51,8 @@ errno = EOVERFLOW; return 1; } - struct cx_mempool_memory_s **newdata = realloc(pool->data, newmsize); + struct cx_mempool_memory_s **newdata = cxRealloc( + cxDefaultAllocator, pool->data, newmsize); if (newdata == NULL) return 1; pool->data = newdata; pool->capacity = newcap; @@ -68,7 +69,8 @@ return NULL; } - struct cx_mempool_memory_s *mem = malloc(sizeof(cx_destructor_func) + n); + struct cx_mempool_memory_s *mem = cxMalloc( + cxDefaultAllocator, sizeof(cx_destructor_func) + n); if (mem == NULL) return NULL; mem->destructor = pool->auto_destr; @@ -103,7 +105,7 @@ 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)); + newm = cxRealloc(cxDefaultAllocator, mem, n + sizeof(cx_destructor_func)); if (newm == NULL) return NULL; if (mem != newm) { @@ -134,7 +136,7 @@ if (mem->destructor) { mem->destructor(mem->c); } - free(mem); + cxFree(cxDefaultAllocator, mem); size_t last_index = pool->size - 1; if (i != last_index) { pool->data[i] = pool->data[last_index]; @@ -155,11 +157,11 @@ if (mem->destructor) { mem->destructor(mem->c); } - free(mem); + cxFree(cxDefaultAllocator, mem); } - free(pool->data); - free((void*) pool->allocator); - free(pool); + cxFree(cxDefaultAllocator, pool->data); + cxFree(cxDefaultAllocator, (void*) pool->allocator); + cxFree(cxDefaultAllocator, pool); } void cxMempoolSetDestructor( @@ -219,12 +221,12 @@ } struct cx_mempool_s *pool = - malloc(sizeof(struct cx_mempool_s)); + cxMalloc(cxDefaultAllocator, sizeof(struct cx_mempool_s)); if (pool == NULL) return NULL; - CxAllocator *provided_allocator = malloc(sizeof(CxAllocator)); + CxAllocator *provided_allocator = cxMalloc(cxDefaultAllocator, sizeof(CxAllocator)); if (provided_allocator == NULL) { // LCOV_EXCL_START - free(pool); + cxFree(cxDefaultAllocator, pool); return NULL; } // LCOV_EXCL_STOP provided_allocator->cl = &cx_mempool_allocator_class; @@ -232,10 +234,10 @@ pool->allocator = provided_allocator; - pool->data = malloc(poolsize); + pool->data = cxMalloc(cxDefaultAllocator, poolsize); if (pool->data == NULL) { // LCOV_EXCL_START - free(provided_allocator); - free(pool); + cxFree(cxDefaultAllocator, provided_allocator); + cxFree(cxDefaultAllocator, pool); return NULL; } // LCOV_EXCL_STOP @@ -246,6 +248,10 @@ return pool; } +static void cx_mempool_free_transferred_allocator(void *al) { + cxFree(cxDefaultAllocator, al); +} + int cxMempoolTransfer( CxMempool *source, CxMempool *dest @@ -259,7 +265,7 @@ } // allocate a replacement allocator for the source pool - CxAllocator *new_source_allocator = malloc(sizeof(CxAllocator)); + CxAllocator *new_source_allocator = cxMalloc(cxDefaultAllocator, sizeof(CxAllocator)); if (new_source_allocator == NULL) { // LCOV_EXCL_START return 1; } // LCOV_EXCL_STOP @@ -274,7 +280,7 @@ // we have to remove const-ness for this, but that's okay here CxAllocator *transferred_allocator = (CxAllocator*) source->allocator; transferred_allocator->data = dest; - cxMempoolRegister(dest, transferred_allocator, free); + cxMempoolRegister(dest, transferred_allocator, cx_mempool_free_transferred_allocator); // prepare the source pool for re-use source->allocator = new_source_allocator;
--- a/src/printf.c Thu May 15 15:43:30 2025 +0200 +++ b/src/printf.c Thu May 15 16:02:54 2025 +0200 @@ -68,7 +68,7 @@ return (int) wfc(buf, 1, ret, stream); } else { int len = ret + 1; - char *newbuf = malloc(len); + char *newbuf = cxMalloc(cxDefaultAllocator, 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); + cxFree(cxDefaultAllocator, 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;
--- a/src/properties.c Thu May 15 15:43:30 2025 +0200 +++ b/src/properties.c Thu May 15 16:02:54 2025 +0200 @@ -287,7 +287,7 @@ cx_attr_unused CxProperties *prop, CxPropertiesSource *src ) { - src->data_ptr = malloc(src->data_size); + src->data_ptr = cxMalloc(cxDefaultAllocator, 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); + cxFree(cxDefaultAllocator, src->data_ptr); } CxPropertiesSource cxPropertiesStringSource(cxstring str) {
--- a/src/streams.c Thu May 15 15:43:30 2025 +0200 +++ b/src/streams.c Thu May 15 16:02:54 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 = cxMalloc(cxDefaultAllocator, bufsize); if (lbuf == NULL) return 0; } @@ -74,7 +75,7 @@ } if (lbuf != buf) { - free(lbuf); + cxFree(cxDefaultAllocator, lbuf); } return ncp;
--- a/src/string.c Thu May 15 15:43:30 2025 +0200 +++ b/src/string.c Thu May 15 16:02:54 2025 +0200 @@ -65,7 +65,7 @@ void cx_strfree(cxmutstr *str) { if (str == NULL) return; - free(str->ptr); + cxFree(cxDefaultAllocator, str->ptr); str->ptr = NULL; str->length = 0; } @@ -286,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 + ? cxCalloc(cxDefaultAllocator, needle.length + 1, sizeof(size_t)) + : s_prefix_table; // keep counter in registers register size_t i, j; @@ -325,7 +326,7 @@ // if prefix table was allocated on the heap, free it if (useheap) { - free(ptable); + cxFree(cxDefaultAllocator, ptable); } return result;
--- a/src/tree.c Thu May 15 15:43:30 2025 +0200 +++ b/src/tree.c Thu May 15 16:02:54 2025 +0200 @@ -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); + cxFree(cxDefaultAllocator, 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 = cxMalloc(cxDefaultAllocator, 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 = cxMalloc(cxDefaultAllocator, 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 = cxMalloc(cxDefaultAllocator, 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); + cxFree(cxDefaultAllocator, q); } // increment the node counter
--- a/tests/test_allocator.c Thu May 15 15:43:30 2025 +0200 +++ b/tests/test_allocator.c Thu May 15 16:02:54 2025 +0200 @@ -36,12 +36,6 @@ #include "cx/allocator.h" #include <errno.h> -CX_TEST(test_allocator_default) { - CX_TEST_DO { - CX_TEST_ASSERT(cxDefaultAllocator->cl != NULL); - } -} - CX_TEST(test_allocator_default_malloc) { void *test = cxMalloc(cxDefaultAllocator, 16); CX_TEST_DO { @@ -49,7 +43,7 @@ // we cannot assert sth. but valgrind will detect an error memcpy(test, "0123456789ABCDEF", 16); } - free(test); + cxFree(cxDefaultAllocator, test); } CX_TEST(test_allocator_default_calloc) { @@ -60,33 +54,33 @@ CX_TEST_ASSERT(test[i] == 0); } } - free(test); + cxFree(cxDefaultAllocator, test); } CX_TEST(test_allocator_default_realloc) { - char *test = calloc(8, 1); + char *test = cxCalloc(cxDefaultAllocator, 8, 1); memcpy(test, "Test", 5); CX_TEST_DO { test = cxRealloc(cxDefaultAllocator, test, 16); CX_TEST_ASSERT(test != NULL); CX_TEST_ASSERT(0 == strcmp(test, "Test")); } - free(test); + cxFree(cxDefaultAllocator, test); } CX_TEST(test_allocator_default_reallocarray) { - char *test = calloc(8, 1); + char *test = cxCalloc(cxDefaultAllocator, 8, 1); memcpy(test, "Test", 5); CX_TEST_DO { test = cxReallocArray(cxDefaultAllocator, test, 16, 2); CX_TEST_ASSERT(test != NULL); CX_TEST_ASSERT(0 == strcmp(test, "Test")); } - free(test); + cxFree(cxDefaultAllocator, test); } CX_TEST(test_allocator_default_reallocarray_overflow) { - char *test = calloc(8, 1); + char *test = cxCalloc(cxDefaultAllocator, 8, 1); memcpy(test, "Test", 5); CX_TEST_DO { void *fail = cxReallocArray(cxDefaultAllocator, test, SIZE_MAX/2, 4); @@ -94,7 +88,7 @@ CX_TEST_ASSERT(fail == NULL); CX_TEST_ASSERT(0 == strcmp(test, "Test")); } - free(test); + cxFree(cxDefaultAllocator, test); } CX_TEST(test_allocator_default_free) { @@ -107,7 +101,7 @@ } CX_TEST(test_allocator_reallocate) { - char *test = calloc(8, 1); + char *test = cxCalloc(cxDefaultAllocator, 8, 1); memcpy(test, "Test", 5); CX_TEST_DO { int ret = cxReallocate(cxDefaultAllocator, &test, 16); @@ -115,7 +109,7 @@ CX_TEST_ASSERT(test != NULL); CX_TEST_ASSERT(0 == strcmp(test, "Test")); } - free(test); + cxFree(cxDefaultAllocator, test); } CX_TEST(test_allocator_reallocate_low_level) { @@ -131,7 +125,7 @@ } CX_TEST(test_allocator_reallocatearray) { - char *test = calloc(8, 1); + char *test = cxCalloc(cxDefaultAllocator, 8, 1); memcpy(test, "Test", 5); CX_TEST_DO { int ret = cxReallocateArray(cxDefaultAllocator, &test, 16, 2); @@ -139,11 +133,11 @@ CX_TEST_ASSERT(test != NULL); CX_TEST_ASSERT(0 == strcmp(test, "Test")); } - free(test); + cxFree(cxDefaultAllocator, test); } CX_TEST(test_allocator_reallocatearray_overflow) { - char *test = calloc(8, 1); + char *test = cxCalloc(cxDefaultAllocator, 8, 1); memcpy(test, "Test", 5); CX_TEST_DO { int ret = cxReallocateArray(cxDefaultAllocator, &test, SIZE_MAX/2, 4); @@ -152,7 +146,7 @@ CX_TEST_ASSERT(test != NULL); CX_TEST_ASSERT(0 == strcmp(test, "Test")); } - free(test); + cxFree(cxDefaultAllocator, test); } CX_TEST(test_allocator_reallocatearray_low_level) { @@ -211,7 +205,6 @@ CxTestSuite *cx_test_suite_allocator(void) { CxTestSuite *suite = cx_test_suite_new("allocator"); - cx_test_register(suite, test_allocator_default); cx_test_register(suite, test_allocator_default_malloc); cx_test_register(suite, test_allocator_default_calloc); cx_test_register(suite, test_allocator_default_realloc);
--- a/tests/test_hash_map.c Thu May 15 15:43:30 2025 +0200 +++ b/tests/test_hash_map.c Thu May 15 16:02:54 2025 +0200 @@ -665,8 +665,8 @@ // verify that all pairs are present in the reference map for (size_t i = 0 ; i < map->collection.size ; i++) { CX_TEST_ASSERT(test_map_reference_get(pairs[i].key) == pairs[i].value); - // this was strdup'ed - free((void*)pairs[i].key); + // this was cx_strdup'ed + cxFree(cxDefaultAllocator, (void*)pairs[i].key); } free(pairs); }
--- a/tests/test_list.c Thu May 15 15:43:30 2025 +0200 +++ b/tests/test_list.c Thu May 15 16:02:54 2025 +0200 @@ -38,7 +38,7 @@ CX_TEST(test_array_add) { CX_ARRAY_DECLARE(int, arr); - arr = calloc(5, sizeof(int)); + arr = cxCalloc(cxDefaultAllocator, 5, sizeof(int)); arr[0] = 2; arr[1] = 3; arr[2] = 5; @@ -71,12 +71,12 @@ CX_TEST_ASSERT(arr_size == 6); CX_TEST_ASSERT(arr_capacity >= 6); } - free(arr); + cxFree(cxDefaultAllocator, arr); } CX_TEST(test_array_add8) { CX_ARRAY_DECLARE_SIZED(int, arr, uint8_t); - arr = calloc(5, sizeof(int)); + arr = cxCalloc(cxDefaultAllocator, 5, sizeof(int)); arr[0] = 2; arr[1] = 3; arr[2] = 5; @@ -115,7 +115,7 @@ CX_TEST_ASSERT(arr_size == 6); CX_TEST_ASSERT(arr_capacity < 128); } - free(arr); + cxFree(cxDefaultAllocator, arr); } CX_TEST(test_array_copy_unsupported_width) { @@ -139,7 +139,7 @@ CX_TEST_ASSERT(arr_size == 0); CX_TEST_ASSERT(arr_capacity == 16); } - free(arr); + cxFree(cxDefaultAllocator, arr); } CX_TEST(test_array_reserve) { @@ -158,7 +158,7 @@ CX_TEST_ASSERT(arr_size == 5); CX_TEST_ASSERT(arr_capacity >= 25); } - free(arr); + cxFree(cxDefaultAllocator, arr); } CX_TEST(test_array_insert_sorted) { @@ -206,7 +206,7 @@ CX_TEST_ASSERT(0 == memcmp(array, expected, 18 * sizeof(int))); } - free(array); + cxFree(cxDefaultAllocator, array); } CX_TEST(test_array_binary_search) {
--- a/tests/test_tree.c Thu May 15 15:43:30 2025 +0200 +++ b/tests/test_tree.c Thu May 15 16:02:54 2025 +0200 @@ -1503,7 +1503,7 @@ CX_TEST_ASSERT(node != NULL); CX_TEST_ASSERT(node->parent == NULL); CX_TEST_ASSERT(node->children == NULL); - free(node); + cxFree(cxDefaultAllocator, node); node = NULL; size_t added = cx_tree_add_array( "/", 1, sizeof(const char *), @@ -1516,7 +1516,7 @@ CX_TEST_ASSERT(node != NULL); CX_TEST_ASSERT(node->parent == NULL); CX_TEST_ASSERT(node->children == NULL); - free(node); + cxFree(cxDefaultAllocator, node); } } @@ -1535,7 +1535,7 @@ CX_TEST_ASSERT(result == 0); CX_TEST_ASSERT(root.children == node); CX_TEST_ASSERT(node->parent == &root); - free(node); + cxFree(cxDefaultAllocator, node); } }
--- a/tests/ucxtest.c Thu May 15 15:43:30 2025 +0200 +++ b/tests/ucxtest.c Thu May 15 16:02:54 2025 +0200 @@ -26,6 +26,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#include "util_allocator.h" #include "cx/common.h" #include "cx/test.h" @@ -52,16 +53,35 @@ CxTestSuite *cx_test_suite_printf(void); CxTestSuite *cx_test_suite_mempool(void); -#define run_tests(suite) cx_test_run_stdout(suite); success += (suite)->success; failure += (suite)->failure +#define run_tests(suite) cx_test_run_stdout(suite); success += (suite)->success; failure += (suite)->failure; \ + if (!cx_testing_allocator_verify(&testing_allocator)) break; #define execute_test_suites(...) unsigned success = 0, failure = 0; CxTestSuite* test_suites[] = {__VA_ARGS__}; \ for (size_t i = 0; i < cx_nmemb(test_suites) ; i++) {run_tests(test_suites[i]);} (void)0 #define free_test_suites for (size_t i = 0 ; i < cx_nmemb(test_suites) ; i++) {cx_test_suite_free(test_suites[i]);} (void)0 +static int verify_testing_allocator() { + printf("Verify Testing Allocator\n"); + CxTestSuite *suite_ta = cx_test_suite_testing_allocator(); + int result = suite_ta->failure > 0; + cx_test_suite_free(suite_ta); + return result; +} + int main(void) { + if (verify_testing_allocator()) { + fprintf(stderr, "Testing Allocator is not working - cannot perform tests!\n"); + return EXIT_FAILURE; + } + + // Replace the default allocator with the testing allocator + CxTestingAllocator testing_allocator; + cx_testing_allocator_init(&testing_allocator); + cxDefaultAllocator = &testing_allocator.base; + + // Run tests printf("UCX Tests\n---------\n"); execute_test_suites( - cx_test_suite_testing_allocator(), cx_test_suite_szmul(), cx_test_suite_allocator(), cx_test_suite_streams(), @@ -89,6 +109,17 @@ success + failure, success, failure); free_test_suites; + if (cx_testing_allocator_verify(&testing_allocator)) { + printf("\nAllocations verified successfully.\n"); + } else { + printf("\nVerifying allocations failed!\nSome cxMalloc() might not match a cxFree().\n" + "Aborting test execution.\nError must be in a function tested by the last executed test suite.\n" + "Total allocations: %u\n" + "Total frees: %u\n", testing_allocator.alloc_total, testing_allocator.free_total); + failure++; + } + cx_testing_allocator_destroy(&testing_allocator); + return failure > 0 ? 1 : 0; }