docs/Writerside/topics/allocator.h.md

Sun, 23 Nov 2025 13:15:19 +0100

author
Mike Becker <universe@uap-core.de>
date
Sun, 23 Nov 2025 13:15:19 +0100
changeset 1508
dfc0ddd9571e
parent 1446
5732947cbcc2
permissions
-rw-r--r--

optimize sorted insertion by using the infimum instead of the supremum

The reason is that the supremum returns the equal element with the smallest index, and we want the largest.
Therefore, we use the infimum, which already gives us the largest index when there are equal elements, and increase the index by one. The infimum is also guaranteed to exist in that case.

# 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](mempool.h.md) implementation.
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
#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(...)
```

> 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

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:

```C
// 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.

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

> Please pay special attention to always use `cxFree()` and the  `cxRealloc()`-family of functions
> with the **same** allocator that was used to allocate the memory.
{style="warning"}

## 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:

```c

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.

```C
#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](collection.h.md)),
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](mempool.h.md) or deallocations of deeply nested [JSON](json.h.md) objects.

> Destructor functions are not to be confused with `free()`-like functions.
> The fundamental differences are that 
> * it is not safe to pass `NULL` to a destructor function
> * a destructor may only deallocate the contents inside an object but not the object itself, depending on context
>
{style="note"}

> For example, when you are using a [list](list.h.md) that stores elements directly, a destructor function
> assigned to that collection may only destroy the element's contents but must not deallocate the element's memory.
> On the other hand, when the list is storing just pointers to the elements, you _may_ want the destructor
> function to also deallocate the element's memory when the element is removed from that list.

## Clone Function

```C
#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.

```C
#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](list.h.md#clone) or [maps](map.h.md#clone).

<seealso>
<category ref="apidoc">
<a href="https://ucx.sourceforge.io/api/allocator_8h.html">allocator.h</a>
</category>
</seealso>

mercurial