UNIXworkcode

1 # Allocator 2 3 The allocator interface provides a mechanism to implement own custom allocators 4 that can also be used in many other functions in UCX. 5 6 A default allocator implementation using the stdlib functions is 7 available via the global symbol `cxStdlibAllocator`, 8 and UCX also provides a [memory pool](mempool.h.md) implementation. 9 You are free to add your additional, own custom implementations. 10 A general sketch that illustrates how to do this can be found [below](#custom-allocator). 11 12 ## Default Allocator 13 14 The global default allocator which is used by UCX 15 when no specific allocator is specified 16 can be configured via the `cxDefaultAllocator`. 17 It is by default set to the `cxStdlibAllocator`. 18 19 ## Overview 20 21 ```C 22 #include <cx/allocator.h> 23 24 void *cxMalloc(const CxAllocator *allocator, size_t n); 25 26 void *cxZalloc(const CxAllocator *allocator, size_t n); 27 28 void *cxCalloc(const CxAllocator *allocator, 29 size_t nmemb, size_t size); 30 31 void *cxRealloc(const CxAllocator *allocator, void *mem, size_t n); 32 33 void *cxReallocArray(const CxAllocator *allocator, void *mem, 34 size_t nmemb, size_t size); 35 36 int cxReallocate(const CxAllocator *allocator, void **mem, size_t n); 37 38 int cxReallocateArray(const CxAllocator *allocator, void **mem, 39 size_t nmemb, size_t size); 40 41 void cxFree(const CxAllocator *allocator, void *mem); 42 43 void cx_system_page_size(void); 44 45 void *cx_zalloc(size_t n); 46 47 int cx_reallocate(void **mem, size_t n); 48 49 int cx_reallocatearray(void **mem, size_t nmemb, size_t size); 50 51 // predefined allocator that uses stdlib functions 52 CxAllocator * const cxStdlibAllocator; 53 54 // default allocator that can be changed 55 CxAllocator *cxDefaultAllocator = cxStdlibAllocator; 56 57 // convenience macros that invoke the above with cxDefaultAllocator 58 #define cxMallocDefault(...) 59 #define cxZallocDefault(...) 60 #define cxCallocDefault(...) 61 #define cxReallocDefault(...) 62 #define cxReallocateDefault(...) 63 #define cxReallocateArrayDefault(...) 64 #define cxReallocArrayDefault(...) 65 #define cxFreeDefault(...) 66 ``` 67 68 > All UCX functions that are not _explicitly_ designed for taking an allocator argument 69 > (recognizable by a `_a` suffix in the function's name) do support a `NULL` argument 70 > in which case the `cxDefaultAllocator` will be used. 71 > 72 > You may change the default allocator at any time, but it is strongly recommended to 73 > do it only once at program start to avoid accidentally freeing memory that was 74 > allocated by a different allocator. 75 76 ## Description 77 78 The functions `cxMalloc()`, `cxCalloc()`, `cxRealloc()`, `cxReallocArray()`, and `cxFree()` 79 invoke the memory management functions specified in the `allocator` and should behave like 80 their respective stdlibc pendants. 81 Implementations of the allocator interface are strongly encouraged to guarantee this behavior, 82 most prominently that invocations of `cxFree()` with a `NULL`-pointer for `mem` are ignored 83 instead of causing segfault error. 84 85 The functions `cxZalloc()` and `cx_zalloc()` allocate memory and set every allocated byte to zero. 86 The latter is merely a macro for stdlibc `calloc(1,n)`. 87 88 Additionally, UCX provides the functions `cxReallocate()` and `cxReallocateArray()`, as well as 89 their independent pendants `cx_reallocate()` and `cx_reallocatearray()`. 90 All those functions solve the problem that a possible reallocation might fail, 91 leading to a quite common programming mistake: 92 93 ```C 94 // common mistake - mem will be lost hen realloc() returns NULL 95 mem = realloc(mem, capacity + 32); 96 if (mem == NULL) // ... do error handling 97 ``` 98 99 The above code can be replaced with `cx_reallocate()` which keeps the pointer intact and returns an error code instead. 100 101 ```C 102 // when cx_reallocate() fails, mem will still point to the old memory 103 if (cx_reallocate(&mem, capacity + 32)) // ... do error handling 104 ``` 105 106 > Please pay special attention to always use `cxFree()` and the `cxRealloc()`-family of functions 107 > with the **same** allocator that was used to allocate the memory. 108 {style="warning"} 109 110 The function `cx_system_page_size()` offers a cross-platform way to retrieve the memory page size in bytes. 111 If, for some reason, the page size cannot be determined, a default of 4096 bytes is returned. 112 113 ## Custom Allocator 114 115 If you want to define your own allocator, you need to initialize the `CxAllocator` structure 116 with a pointer to an allocator class (containing function pointers for the memory management 117 functions) and an optional pointer to custom data. An example is shown below: 118 119 ```c 120 121 struct my_allocator_state { 122 // ... some internal state ... 123 }; 124 125 static cx_allocator_class my_allocator_class = { 126 my_malloc_impl, 127 my_realloc_impl, // all these functions are somewhere defined 128 my_calloc_impl, 129 my_free_impl 130 }; 131 132 CxAllocator create_my_allocator(void) { 133 CxAllocator alloc; 134 alloc.cl = &my_allocator_class; 135 struct my_allocator_state *state = malloc(sizeof(*state)); 136 // ... initialize state ... 137 alloc.data = state; 138 return alloc; 139 } 140 141 void destroy_my_allocator(CxAllocator *al) { 142 struct my_allocator_state *state = al->state; 143 // ... destroy state -- 144 free(state); 145 } 146 ``` 147 148 When you are implementing 149 150 ## Destructor Functions 151 152 The `allocator.h` header also declares two function pointers for destructor functions. 153 154 ```C 155 #include <cx/allocator.h> 156 157 typedef void (*cx_destructor_func)(void *memory); 158 typedef void (*cx_destructor_func2)(void *data, void *memory); 159 ``` 160 161 The first one is called _simple_ destructor (e.g. in the context of [collections](collection.h.md)), 162 and the second one is called _advanced_ destructor. 163 The only difference is that you can pass additional custom `data` to an advanced destructor function. 164 165 Destructor functions play a vital role in deep deallocations. 166 Another scenario, besides destroying elements in a collection, is the deallocation of objects 167 stored in a [memory pool](mempool.h.md) or deallocations of deeply nested [JSON](json.h.md) objects. 168 169 > Destructor functions are not to be confused with `free()`-like functions. 170 > The fundamental differences are that 171 > * it is not safe to pass `NULL` to a destructor function 172 > * a destructor may only deallocate the contents inside an object but not the object itself, depending on context 173 > 174 {style="note"} 175 176 > For example, when you are using a [list](list.h.md) that stores elements directly, a destructor function 177 > assigned to that collection may only destroy the element's contents but must not deallocate the element's memory. 178 > On the other hand, when the list is storing just pointers to the elements, you _may_ want the destructor 179 > function to also deallocate the element's memory when the element is removed from that list. 180 181 ## Clone Function 182 183 ```C 184 #include <cx/allocator.h> 185 186 typedef void*(cx_clone_func)( 187 void *target, const void *source, 188 const CxAllocator *allocator, 189 void *data); 190 ``` 191 192 A clone function is a callback invoked when a deep copy of an object is supposed to be made. 193 If `target` is not `NULL`, memory for the first level was already allocated, and only the deeper levels need to 194 be allocated by the clone function. 195 If `target` is `NULL`, the clone function must allocate all memory. 196 The `source` pointer is never `NULL` and points to the source object. 197 The optional `data` pointer can be used to pass additional state. 198 199 All allocations should be performed by the specified allocator, which is guaranteed to be not `NULL`. 200 201 A very basic clone function that performs a deep copy of a struct with two strings is shown below. 202 203 ```C 204 #include <cx/string.h> 205 #include <cx/allocator.h> 206 207 struct kv_pair { 208 cxmutstr key; 209 cxmutstr value; 210 }; 211 212 void *clone_kv_pair( 213 void *target, const void *source, 214 const CxAllocator *allocator, 215 [[maybe_unused]] void *data) { 216 const struct kv_pair *src = source; 217 struct kv_pair *dst; 218 if (target == NULL) { 219 dst = cxMalloc(allocator, sizeof(struct kv_pair)); 220 } else { 221 dst = target; 222 } 223 dst->key = cx_strdup_a(allocator, src->key); 224 dst->value = cx_strdup_a(allocator, src->value); 225 return dst; 226 } 227 ``` 228 229 Clone functions are, for example, used by the functions to clone [lists](list.h.md#clone) or [maps](map.h.md#clone). 230 231 <seealso> 232 <category ref="apidoc"> 233 <a href="https://ucx.sourceforge.io/api/allocator_8h.html">allocator.h</a> 234 </category> 235 </seealso> 236