UAP Common Extensions 3.2 Help

Allocator

The allocator interface provides a mechanism to implement own custom allocators that can also be used in many other functions in UCX.

A default allocator implementation using the stdlib functions is available via the global symbol cxStdlibAllocator, and UCX also provides a memory pool implementation. You are free to add your additional, own custom implementations. A general sketch that illustrates how to do this can be found below.

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

#include <cx/allocator.h> void *cxMalloc(const CxAllocator *allocator, size_t n); void *cxZalloc(const CxAllocator *allocator, size_t n); void *cxCalloc(const CxAllocator *allocator, size_t nmemb, size_t size); void *cxRealloc(const CxAllocator *allocator, void *mem, size_t n); void *cxReallocArray(const CxAllocator *allocator, void *mem, size_t nmemb, size_t size); int cxReallocate(const CxAllocator *allocator, void **mem, size_t n); int cxReallocateArray(const CxAllocator *allocator, void **mem, size_t nmemb, size_t size); void cxFree(const CxAllocator *allocator, void *mem); void *cx_zalloc(size_t n); int cx_reallocate(void **mem, size_t n); int cx_reallocatearray(void **mem, size_t nmemb, size_t size); // predefined allocator that uses stdlib functions CxAllocator * const cxStdlibAllocator; // default allocator that can be changed CxAllocator *cxDefaultAllocator = cxStdlibAllocator; // convenience macros that invoke the above with cxDefaultAllocator #define cxMallocDefault(...) #define cxZallocDefault(...) #define cxCallocDefault(...) #define cxReallocDefault(...) #define cxReallocateDefault(...) #define cxReallocateArrayDefault(...) #define cxReallocArrayDefault(...) #define cxFreeDefault(...)

Description

The functions cxMalloc(), cxCalloc(), cxRealloc(), cxReallocArray(), and cxFree() invoke the memory management functions specified in the allocator and should behave like their respective stdlibc pendants. Implementations of the allocator interface are strongly encouraged to guarantee this behavior, most prominently that invocations of cxFree() with a NULL-pointer for mem are ignored instead of causing segfault error.

The functions cxZalloc() and cx_zalloc() allocate memory and set every allocated byte to zero. The latter is merely a macro for stdlibc calloc(1,n).

Additionally, UCX provides the functions cxReallocate() and cxReallocateArray(), as well as their independent pendants cx_reallocate() and cx_reallocatearray(). All those functions solve the problem that a possible reallocation might fail, leading to a quite common programming mistake:

// common mistake - mem will be lost hen realloc() returns NULL mem = realloc(mem, capacity + 32); if (mem == NULL) // ... do error handling

The above code can be replaced with cx_reallocate() which keeps the pointer intact and returns an error code instead.

// when cx_reallocate() fails, mem will still point to the old memory if (cx_reallocate(&mem, capacity + 32)) // ... do error handling

Custom Allocator

If you want to define your own allocator, you need to initialize the CxAllocator structure with a pointer to an allocator class (containing function pointers for the memory management functions) and an optional pointer to custom data. An example is shown below:

struct my_allocator_state { // ... some internal state ... }; static cx_allocator_class my_allocator_class = { my_malloc_impl, my_realloc_impl, // all these functions are somewhere defined my_calloc_impl, my_free_impl }; CxAllocator create_my_allocator(void) { CxAllocator alloc; alloc.cl = &my_allocator_class; struct my_allocator_state *state = malloc(sizeof(*state)); // ... initialize state ... alloc.data = state; return alloc; } void destroy_my_allocator(CxAllocator *al) { struct my_allocator_state *state = al->state; // ... destroy state -- free(state); }

When you are implementing

Destructor Functions

The allocator.h header also declares two function pointers for destructor functions.

#include <cx/allocator.h> typedef void (*cx_destructor_func)(void *memory); typedef void (*cx_destructor_func2)(void *data, void *memory);

The first one is called simple destructor (e.g. in the context of collections), and the second one is called advanced destructor. The only difference is that you can pass additional custom data to an advanced destructor function.

Destructor functions play a vital role in deep deallocations. Another scenario, besides destroying elements in a collection, is the deallocation of objects stored in a memory pool or deallocations of deeply nested JSON objects.

Clone Function

#include <cx/allocator.h> typedef void*(cx_clone_func)( void *target, const void *source, const CxAllocator *allocator, void *data);

A clone function is a callback invoked when a deep copy of an object is supposed to be made. If target is not NULL, memory for the first level was already allocated, and only the deeper levels need to be allocated by the clone function. If target is NULL, the clone function must allocate all memory. The source pointer is never NULL and points to the source object. The optional data pointer can be used to pass additional state.

All allocations should be performed by the specified allocator, which is guaranteed to be not NULL.

A very basic clone function that performs a deep copy of a struct with two strings is shown below.

#include <cx/string.h> #include <cx/allocator.h> struct kv_pair { cxmutstr key; cxmutstr value; }; void *clone_kv_pair( void *target, const void *source, const CxAllocator *allocator, [[maybe_unused]] void *data) { const struct kv_pair *src = source; struct kv_pair *dst; if (target == NULL) { dst = cxMalloc(allocator, sizeof(struct kv_pair)); } else { dst = target; } dst->key = cx_strdup_a(allocator, src->key); dst->value = cx_strdup_a(allocator, src->value); return dst; }

Clone functions are, for example, used by the functions to clone lists or maps.

26 October 2025