UNIXworkcode

1 # Memory Pool 2 3 A memory pool is providing an allocator implementation that automatically deallocates the memory upon its destruction. 4 It also allows you to register destructor functions for the allocated memory, which are automatically called before 5 the memory is deallocated. 6 7 Additionally, you may also register _independent_ destructor functions. 8 This can be useful, for example, when some library allocates memory that you wish to destroy when the memory pool gets destroyed. 9 10 A memory pool can be used with all UCX features that support the use of an [allocator](allocator.h.md). 11 For example, the UCX [string](string.h.md) functions provide several variants suffixed with `_a` for that purpose. 12 13 ## Basic Memory Management 14 15 ```C 16 #include <cx/mempool.h> 17 18 enum cx_mempool_type { 19 CX_MEMPOOL_TYPE_SIMPLE, 20 CX_MEMPOOL_TYPE_ADVANCED, 21 CX_MEMPOOL_TYPE_PURE, 22 }; 23 24 CxMempool *cxMempoolCreate(size_t capacity, 25 enum cx_mempool_type type); 26 27 CxMempool *cxMempoolCreateSimple(size_t capacity); 28 CxMempool *cxMempoolCreateAdvanced(size_t capacity); 29 CxMempool *cxMempoolCreatePure(size_t capacity); 30 31 void cxMempoolFree(CxMempool *pool); 32 33 void cxMempoolGlobalDestructor(CxMempool *pool, 34 cx_destructor_func fnc); 35 36 void cxMempoolGlobalDestructor2(CxMempool *pool, 37 cx_destructor_func2 fnc, void *data); 38 39 void cxMempoolSetDestructor(void *memory, 40 cx_destructor_func fnc); 41 42 void cxMempoolSetDestructor2(void *memory, 43 cx_destructor_func fnc, void *data); 44 45 void cxMempoolRemoveDestructor(void *memory); 46 47 void cxMempoolRemoveDestructor2(void *memory); 48 49 int cxMempoolRegister(CxMempool *pool, void *memory, 50 cx_destructor_func fnc); 51 52 int cxMempoolRegister2(CxMempool *pool, void *memory, 53 cx_destructor_func fnc, void *data); 54 ``` 55 56 A memory pool is created with the `cxMempoolCreate()` family of functions with a default `capacity`. 57 If `capacity` is set to zero, an implementation default is used. 58 59 The `type` specifies how much additional data is allocated for each pooled memory block. 60 A simple pool reserves memory for an optional `cx_destructor_func`. 61 An advanced pool reserves memory for an optional `cx_destructor_func2` 62 and an additional `data` pointer that will be passed to that destructor. 63 A pure pool does not reserve any additional data and therefore does not support registering 64 custom destructors with the allocated memory. 65 66 > After creating a memory pool `CxMempool *mpool`, you can access the provided allocator via `mpool->allocator`. 67 >{style="note"} 68 69 The functions `cxMempoolGlobalDestructor()` and `cxMempoolGlobalDestructor2()` can be used to specify destructor functions 70 that shall be invoked for _all_ objects allocated by the pool when they are freed (see [](#order-of-destruction)). 71 This is usually only useful for pools that will only contain objects of the same type. 72 73 In _simple_ memory pools, the two functions `cxMempoolSetDestructor()` and `cxMempoolRemoveDestructor()` can be used to assign a specific destructor 74 function to an allocated object or remove an assigned destructor function, respectively. 75 The `memory` pointer points to the allocated object, which must have been allocated by any `CxMempool`'s provided allocator. 76 For _advanced_ pools, the functions `cxMempoolSetDestructor2()` and `cxMempoolRemoveDestructor2()` do the same. 77 It is disallowed to use the functions with a pool of the wrong type and will most likely cause undefined behavior. 78 Pure pools do not allow setting destructors for individual memory blocks at all. 79 80 The `cxMempoolRegister()` function allocates a new wrapper object for `memory` 81 and makes the specified destructor function being called when the pool gets destroyed. 82 Alternatively, the `cxMempoolRegister2()` function can be used to register an advanced destructor and a pointer to custom data. 83 Be aware that the memory pointed to by the additional data pointer must remain valid until the pool gets destroyed! 84 Usually these functions return zero except for platforms where memory allocations are likely to fail, 85 in which case a non-zero value is returned. 86 87 > When you register foreign memory with a pool, you can decide which destructor type you want to use, 88 > regardless of the pool's type. 89 > That means, for example, you can use `cxMempoolReigster2()` for simple pools, `cxMempoolRegister()` for pure pools, etc. 90 > 91 > When you use `cxMempoolReigster2()` the `data` pointer must not be `NULL` or the behavior will be undefined when the pool gets destroyed. 92 93 ### Order of Destruction 94 95 When you call `cxMempoolFree()` the following actions are performed: 96 97 1. In any order, for each object allocated by the pool 98 1. the destructor function assigned to that object is called 99 2. the pool's global simple destructor is called 100 3. the pool's global advanced destructor is called 101 4. the object's memory is deallocated 102 2. In any order, for each registered foreign object the destructor is called 103 3. The pool memory is deallocated 104 4. The pool structure is deallocated 105 106 ## Transfer Memory 107 108 ```C 109 #include <cx/mempool.h> 110 111 int cxMempoolTransfer(CxMempool *source, CxMempool *dest); 112 113 int cxMempoolTransferObject(CxMempool *source, CxMempool *dest, 114 const void *obj); 115 ``` 116 117 Memory managed by a pool can be transferred to another pool. 118 119 The function `cxMempoolTransfer()` transfers all memory managed and/or registered with the `source` pool to the `dest` pool. 120 It also registers its allocator with the `dest` pool and creates a new allocator for the `source` pool. 121 That means, that all references to the allocator of the `source` pool remain valid and continue to work with the `dest` pool. 122 The transferred allocator will be destroyed when the `dest` pool gets destroyed. 123 124 The function `cxMempoolTransferObject()` transfers a _single_ object managed by the `source` pool to the `dest` pool. 125 In contrast to transferring an entire pool, if `obj` has a reference to `source->allocator`, it must be updated to `dest->allocator` manually. 126 It is also possible to transfer registered memory from one pool to another, this way. 127 128 The functions return zero when the transfer was successful, and non-zero under one of the following error conditions: 129 - a necessary memory allocation was not possible 130 - the `source` and `dest` pointers point to the same pool 131 - the pools have a different type (simple, advanced, pure) 132 - the pools have different base allocators (i.e. `cxDefaultAllocator` changed between creation of the pools) 133 134 In case of an error, no memory is transferred and both pools remain unchanged and are in a valid state. 135 136 137 ## Example 138 139 The following code illustrates how the contents of a CSV file are read into pooled memory. 140 ```C 141 #include <stdio.h> 142 #include <cx/mempool.h> 143 #include <cx/linked_list.h> 144 #include <cx/string.h> 145 #include <cx/buffer.h> 146 #include <cx/utils.h> 147 148 typedef struct { 149 cxstring column_a; 150 cxstring column_b; 151 cxstring column_c; 152 } CSVData; 153 154 int main(void) { 155 // create a simple pool for various different objects 156 CxMempool* pool = cxMempoolCreateSimple(128); 157 158 FILE *f = fopen("test.csv", "r"); 159 if (f == NULL) { 160 perror("Cannot open file"); 161 return 1; 162 } 163 // close the file automatically at pool destruction 164 cxMempoolRegister(pool, f, (cx_destructor_func) fclose); 165 166 // create a buffer using the memory pool for destruction 167 CxBuffer *content = cxBufferCreate( 168 NULL, 256, pool->allocator, CX_BUFFER_AUTO_EXTEND 169 ); 170 171 // read the file into the buffer and turn it into a string 172 cx_stream_copy( 173 f, content, (cx_read_func) fread, cxBufferWriteFunc 174 ); 175 fclose(f); 176 cxstring contentstr = cx_strn(content->space, content->size); 177 178 // split the string into lines 179 // use the memory pool to allocate the target array 180 cxstring* lines; 181 size_t lc = cx_strsplit_a( 182 pool->allocator, contentstr, cx_str("\n"), SIZE_MAX, &lines 183 ); 184 185 // skip the header and parse the remaining data into a linked list 186 // the nodes of the list shall also be allocated by the pool 187 CxList* datalist = cxLinkedListCreate( 188 pool->allocator, NULL, sizeof(CSVData) 189 ); 190 for (size_t i = 1 ; i < lc ; i++) { 191 if (lines[i].length == 0) continue; 192 cxstring fields[3]; 193 size_t fc = cx_strsplit(lines[i], cx_str(";"), 3, fields); 194 if (fc != 3) { 195 fprintf(stderr, "Syntax error in line %zu.\n", i); 196 cxMempoolFree(pool); 197 return 1; 198 } 199 CSVData data; 200 data.column_a = fields[0]; 201 data.column_b = fields[1]; 202 data.column_c = fields[2]; 203 cxListAdd(datalist, &data); 204 } 205 206 // iterate through the list and output the data 207 CxIterator iter = cxListIterator(datalist); 208 cx_foreach(CSVData*, data, iter) { 209 printf("Column A: %" CX_PRIstr " | " 210 "Column B: %" CX_PRIstr " | " 211 "Column C: %" CX_PRIstr "\n", 212 CX_SFMT(data->column_a), 213 CX_SFMT(data->column_b), 214 CX_SFMT(data->column_c) 215 ); 216 } 217 218 // cleanup everything, no manual free() needed 219 cxMempoolFree(pool); 220 221 return 0; 222 } 223 ``` 224 225 <seealso> 226 <category ref="apidoc"> 227 <a href="https://ucx.sourceforge.io/api/mempool_8h.html">mempool.h</a> 228 </category> 229 </seealso> 230