UAP Common Extensions 3.2 Help

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. For example, the UCX string functions provide several variants suffixed with _a for that purpose.

Basic Memory Management

#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.

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.

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

#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.

#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; }
23 May 2025