Fri, 23 May 2025 14:00:24 +0200
add base allocator to mempool
otherwise, mempools could not be used as
new default allocators
# Memory Pool A memory pool is providing an allocator implementation that automatically deallocates the memory upon its destruction. It also allows you to register destructor functions for the allocated memory, which are automatically called before the memory is deallocated. Additionally, you may also register _independent_ destructor functions. This can be useful, for example, when some library allocates memory that you wish to destroy when the memory pool gets destroyed. A memory pool can be used with all UCX features that support the use of an [allocator](allocator.h.md). For example, the UCX [string](string.h.md) functions provide several variants suffixed with `_a` for that purpose. ## Basic Memory Management ```C #include <cx/mempool.h> enum cx_mempool_type { CX_MEMPOOL_TYPE_SIMPLE, CX_MEMPOOL_TYPE_ADVANCED, CX_MEMPOOL_TYPE_PURE, }; CxMempool *cxMempoolCreate(size_t capacity, enum cx_mempool_type type); CxMempool *cxMempoolCreateSimple(size_t capacity); CxMempool *cxMempoolCreateAdvanced(size_t capacity); CxMempool *cxMempoolCreatePure(size_t capacity); void cxMempoolFree(CxMempool *pool); void cxMempoolGlobalDestructor(CxMempool *pool, cx_destructor_func fnc); void cxMempoolGlobalDestructor2(CxMempool *pool, cx_destructor_func2 fnc, void *data); void cxMempoolSetDestructor(void *memory, cx_destructor_func fnc); void cxMempoolSetDestructor2(void *memory, cx_destructor_func fnc, void *data); void cxMempoolRemoveDestructor(void *memory); void cxMempoolRemoveDestructor2(void *memory); int cxMempoolRegister(CxMempool *pool, void *memory, cx_destructor_func fnc); int cxMempoolRegister2(CxMempool *pool, void *memory, cx_destructor_func fnc, void *data); ``` A memory pool is created with the `cxMempoolCreate()` family of functions with a default `capacity`. If `capacity` is set to zero, an implementation default is used. The `type` specifies how much additional data is allocated for each pooled memory block. A simple pool reserves memory for an optional `cx_destructor_func`. An advanced pool reserves memory for an optional `cx_destructor_func2` and an additional `data` pointer that will be passed to that destructor. A pure pool does not reserve any additional data and therefore does not support registering custom destructors with the allocated memory. > After creating a memory pool `CxMempool *mpool`, you can access the provided allocator via `mpool->allocator`. >{style="note"} The functions `cxMempoolGlobalDestructor()` and `cxMempoolGlobalDestructor2()` can be used to specify destructor functions that shall be invoked for _all_ objects allocated by the pool when they are freed (see [](#order-of-destruction)). This is usually only useful for pools that will only contain objects of the same type. In _simple_ memory pools, the two functions `cxMempoolSetDestructor()` and `cxMempoolRemoveDestructor()` can be used to assign a specific destructor function to an allocated object or remove an assigned destructor function, respectively. The `memory` pointer points to the allocated object, which must have been allocated by any `CxMempool`'s provided allocator. For _advanced_ pools, the functions `cxMempoolSetDestructor2()` and `cxMempoolRemoveDestructor2()` do the same. It is disallowed to use the functions with a pool of the wrong type and will most likely cause undefined behavior. Pure pools do not allow setting destructors for individual memory blocks at all. The `cxMempoolRegister()` function allocates a new wrapper object for `memory` and makes the specified destructor function being called when the pool gets destroyed. Alternatively, the `cxMempoolRegister2()` function can be used to register an advanced destructor and a pointer to custom data. Be aware that the memory pointed to by the additional data pointer must remain valid until the pool gets destroyed! Usually these functions return zero except for platforms where memory allocations are likely to fail, in which case a non-zero value is returned. > When you register foreign memory with a pool, you can decide which destructor type you want to use, > regardless of the pool's type. > That means, for example, you can use `cxMempoolReigster2()` for simple pools, `cxMempoolRegister()` for pure pools, etc. > > When you use `cxMempoolReigster2()` the `data` pointer must not be `NULL` or the behavior will be undefined when the pool gets destroyed. ### Order of Destruction When you call `cxMempoolFree()` the following actions are performed: 1. In any order, for each object allocated by the pool 1. the destructor function assigned to that object is called 2. the pool's global simple destructor is called 3. the pool's global advanced destructor is called 4. the object's memory is deallocated 2. In any order, for each registered foreign object the destructor is called 3. The pool memory is deallocated 4. The pool structure is deallocated ## Transfer Memory ```C #include <cx/mempool.h> int cxMempoolTransfer(CxMempool *source, CxMempool *dest); int cxMempoolTransferObject(CxMempool *source, CxMempool *dest, const void *obj); ``` Memory managed by a pool can be transferred to another pool. The function `cxMempoolTransfer()` transfers all memory managed and/or registered with the `source` pool to the `dest` pool. It also registers its allocator with the `dest` pool and creates a new allocator for the `source` pool. That means, that all references to the allocator of the `source` pool remain valid and continue to work with the `dest` pool. The transferred allocator will be destroyed when the `dest` pool gets destroyed. The function `cxMempoolTransferObject()` transfers a _single_ object managed by the `source` pool to the `dest` pool. In contrast to transferring an entire pool, if `obj` has a reference to `source->allocator`, it must be updated to `dest->allocator` manually. It is also possible to transfer registered memory from one pool to another, this way. The functions return zero when the transfer was successful, and non-zero under one of the following error conditions: - a necessary memory allocation was not possible - the `source` and `dest` pointers point to the same pool - the pools have a different type (simple, advanced, pure) - the pools have different base allocators (i.e. `cxDefaultAllocator` changed between creation of the pools) In case of an error, no memory is transferred and both pools remain unchanged and are in a valid state. ## Example The following code illustrates how the contents of a CSV file are read into pooled memory. ```C #include <stdio.h> #include <cx/mempool.h> #include <cx/linked_list.h> #include <cx/string.h> #include <cx/buffer.h> #include <cx/utils.h> typedef struct { cxstring column_a; cxstring column_b; cxstring column_c; } CSVData; int main(void) { // create a simple pool for various different objects CxMempool* pool = cxMempoolCreateSimple(128); FILE *f = fopen("test.csv", "r"); if (f == NULL) { perror("Cannot open file"); return 1; } // close the file automatically at pool destruction cxMempoolRegister(pool, f, (cx_destructor_func) fclose); // create a buffer using the memory pool for destruction CxBuffer *content = cxBufferCreate( NULL, 256, pool->allocator, CX_BUFFER_AUTO_EXTEND ); // read the file into the buffer and turn it into a string cx_stream_copy( f, content, (cx_read_func) fread, cxBufferWriteFunc ); fclose(f); cxstring contentstr = cx_strn(content->space, content->size); // split the string into lines // use the memory pool to allocate the target array cxstring* lines; size_t lc = cx_strsplit_a( pool->allocator, contentstr, cx_str("\n"), SIZE_MAX, &lines ); // skip the header and parse the remaining data into a linked list // the nodes of the list shall also be allocated by the pool CxList* datalist = cxLinkedListCreate( pool->allocator, NULL, sizeof(CSVData) ); for (size_t i = 1 ; i < lc ; i++) { if (lines[i].length == 0) continue; cxstring fields[3]; size_t fc = cx_strsplit(lines[i], cx_str(";"), 3, fields); if (fc != 3) { fprintf(stderr, "Syntax error in line %zu.\n", i); cxMempoolFree(pool); return 1; } CSVData data; data.column_a = fields[0]; data.column_b = fields[1]; data.column_c = fields[2]; cxListAdd(datalist, &data); } // iterate through the list and output the data CxIterator iter = cxListIterator(datalist); cx_foreach(CSVData*, data, iter) { printf("Column A: %" CX_PRIstr " | " "Column B: %" CX_PRIstr " | " "Column C: %" CX_PRIstr "\n", CX_SFMT(data->column_a), CX_SFMT(data->column_b), CX_SFMT(data->column_c) ); } // cleanup everything, no manual free() needed cxMempoolFree(pool); return 0; } ``` <seealso> <category ref="apidoc"> <a href="https://ucx.sourceforge.io/api/mempool_8h.html">mempool.h</a> </category> </seealso>