Sat, 08 Feb 2025 20:38:05 +0100
adds documentation for destructor functions and collections
also invented some new macros for the collection.h
relates to #451
# Allocator The allocator interface provides a mechanism to implement own custom allocators 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` and UCX also provides a [memory pool](mempool.h.md) implementation. You are free to add additional own custom implementations. A general sketch that illustrates how to do this can be found [below](#custom-allocator). ## Overview ```C void *cxMalloc(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); int cx_reallocate(void **mem, size_t size); int cx_reallocatearray(void **mem, size_t nmemb, size_t size); // predefined allocator that uses stdlib functions CxAllocator *cxDefaultAllocator; ``` > 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. ## 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. 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 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 de-allocations. Another scenarios, besides destroying elements in a collection, are the de-allocation of objects stored in a [memory pool](mempool.h.md) or de-allocations 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 de-allocate 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 de-allocate the element's memory when the element is removed from that list. <seealso> <category ref="apidoc"> <a href="https://ucx.sourceforge.io/api/allocator_8h.html">allocator.h</a> </category> </seealso>