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