allow changing the cxDefaultAllocator - resolves #669

Thu, 15 May 2025 16:02:54 +0200

author
Mike Becker <universe@uap-core.de>
date
Thu, 15 May 2025 16:02:54 +0200
changeset 1318
12fa1d37fe48
parent 1317
eeb2fc3850e2
child 1319
aa1f580f8f59

allow changing the cxDefaultAllocator - resolves #669

CHANGELOG file | annotate | diff | comparison | revisions
docs/Writerside/topics/about.md file | annotate | diff | comparison | revisions
docs/Writerside/topics/allocator.h.md file | annotate | diff | comparison | revisions
docs/Writerside/topics/array_list.h.md file | annotate | diff | comparison | revisions
docs/Writerside/topics/compare.h.md file | annotate | diff | comparison | revisions
docs/Writerside/topics/hash_map.h.md file | annotate | diff | comparison | revisions
docs/Writerside/topics/list.h.md file | annotate | diff | comparison | revisions
docs/Writerside/topics/map.h.md file | annotate | diff | comparison | revisions
docs/Writerside/topics/memory.md file | annotate | diff | comparison | revisions
docs/Writerside/topics/string.h.md file | annotate | diff | comparison | revisions
src/allocator.c file | annotate | diff | comparison | revisions
src/array_list.c file | annotate | diff | comparison | revisions
src/buffer.c file | annotate | diff | comparison | revisions
src/cx/allocator.h file | annotate | diff | comparison | revisions
src/cx/array_list.h file | annotate | diff | comparison | revisions
src/cx/buffer.h file | annotate | diff | comparison | revisions
src/cx/hash_map.h file | annotate | diff | comparison | revisions
src/cx/linked_list.h file | annotate | diff | comparison | revisions
src/cx/properties.h file | annotate | diff | comparison | revisions
src/cx/string.h file | annotate | diff | comparison | revisions
src/cx/tree.h file | annotate | diff | comparison | revisions
src/json.c file | annotate | diff | comparison | revisions
src/linked_list.c file | annotate | diff | comparison | revisions
src/list.c file | annotate | diff | comparison | revisions
src/mempool.c file | annotate | diff | comparison | revisions
src/printf.c file | annotate | diff | comparison | revisions
src/properties.c file | annotate | diff | comparison | revisions
src/streams.c file | annotate | diff | comparison | revisions
src/string.c file | annotate | diff | comparison | revisions
src/tree.c file | annotate | diff | comparison | revisions
tests/test_allocator.c file | annotate | diff | comparison | revisions
tests/test_hash_map.c file | annotate | diff | comparison | revisions
tests/test_list.c file | annotate | diff | comparison | revisions
tests/test_tree.c file | annotate | diff | comparison | revisions
tests/ucxtest.c file | annotate | diff | comparison | revisions
--- 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;
 }
 

mercurial