# HG changeset patch # User Mike Becker # Date 1747923932 -7200 # Node ID deccdb82f24e37adee2621e97dcf3e9217cfcbf5 # Parent 7be10b57f658284cec3850c95ad4ee9397e8fd89 improve mempool destructor support (1/3) refactoring while keeping old behavior issue #655 diff -r 7be10b57f658 -r deccdb82f24e src/cx/mempool.h --- a/src/cx/mempool.h Thu May 22 16:23:55 2025 +0200 +++ b/src/cx/mempool.h Thu May 22 16:25:32 2025 +0200 @@ -43,8 +43,52 @@ extern "C" { #endif -/** Internal structure for pooled memory. */ -struct cx_mempool_memory_s; +struct cx_mempool_memory_s { + /** The destructor. */ + cx_destructor_func destructor; + /** The actual memory. */ + char c[]; +}; + +struct cx_mempool_memory2_s { + /** The destructor. */ + cx_destructor_func2 destructor; + /** Data for the destructor. */ + void *data; + /** The actual memory. */ + char c[]; +}; + +struct cx_mempool_foreign_memory_s { + /** The foreign memory. */ + void* mem; + union { + /** Simple destructor. */ + cx_destructor_func destr; + /** Advanced destructor. */ + cx_destructor_func2 destr2; + }; + /** Data for the advanced destructor. */ + void *destr2_data; +}; + +/** Specifies how individual blocks are allocated. */ +enum cx_mempool_type { + /** + * Allows registration of cx_destructor_func for each memory block. + */ + CX_MEMPOOL_TYPE_SIMPLE, + /** + * Allows registration of cx_destructor_func2 for each memory block. + */ + CX_MEMPOOL_TYPE_ADVANCED, + /** + * No individual destructor registration allowed. + * + * In this mode, no additional memory per block is allocated. + */ + CX_MEMPOOL_TYPE_PURE, +}; /** * The basic structure of a memory pool. @@ -54,20 +98,46 @@ /** The provided allocator. */ const CxAllocator *allocator; - /** - * A destructor that shall be automatically registered for newly allocated memory. - * This destructor MUST NOT free the memory. - */ - cx_destructor_func auto_destr; - /** Array of pooled memory. */ - struct cx_mempool_memory_s **data; + void **data; /** Number of pooled memory items. */ size_t size; /** Memory pool capacity. */ size_t capacity; + + /** Array of registered memory. */ + struct cx_mempool_foreign_memory_s *registered; + + /** Number of registered memory items. */ + size_t registered_size; + + /** Capacity for registered memory. */ + size_t registered_capacity; + + /** + * A destructor that shall be called before deallocating a memory block. + * This destructor MUST NOT free the memory itself. + * + * It is guaranteed that this destructor is called after the individual + * destructor of the memory block and before @c destr2. + */ + cx_destructor_func destr; + + /** + * A destructor that shall be called before deallocating a memory block. + * This destructor MUST NOT free the memory itself. + * + * It is guaranteed that this destructor is called after the individual + * destructor of the memory block and @c destr. + */ + cx_destructor_func2 destr2; + + /** + * Additional data for the @c destr2. + */ + void *destr2_data; }; /** @@ -84,19 +154,39 @@ void cxMempoolFree(CxMempool *pool); /** - * Creates an array-based memory pool with a shared destructor function. + * Creates an array-based memory pool. * - * This destructor MUST NOT free the memory. + * The type determines how much additional memory is allocated per block + * to register a destructor function. * - * @param capacity the initial capacity of the pool - * @param destr optional destructor function to use for allocated memory + * @param capacity the initial capacity of the pool (an implementation default if zero) + * @param type the type of memory pool * @return the created memory pool or @c NULL if allocation failed */ cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxMempoolFree, 1) cx_attr_export -CxMempool *cxMempoolCreate(size_t capacity, cx_destructor_func destr); +CxMempool *cxMempoolCreate(size_t capacity, enum cx_mempool_type type); + +/** + * Sets the global destructor for all memory blocks within the specified pool. + * + * @param pool (@c CxMempool*) the memory pool + * @param fnc (@c cx_destructor_func) the destructor that shall be applied to all memory blocks + */ +#define cxMempoolGlobalDestructor(pool, fnc) \ + (pool)->destr = (cx_destructor_func)(fnc) + +/** + * Sets the global destructor for all memory blocks within the specified pool. + * + * @param pool (@c CxMempool*) the memory pool + * @param fnc (@c cx_destructor_func2) the destructor that shall be applied to all memory blocks + * @param data (@c void*) additional data for the destructor function + */ +#define cxMempoolGlobalDestructor2(pool, fnc, data) \ + (pool)->destr2 = (cx_destructor_func2)(fnc); (pool)->destr2_data = (data) /** * Creates a basic array-based memory pool. @@ -104,11 +194,12 @@ * @param capacity (@c size_t) the initial capacity of the pool * @return (@c CxMempool*) the created memory pool or @c NULL if allocation failed */ -#define cxMempoolCreateSimple(capacity) cxMempoolCreate(capacity, NULL) +#define cxMempoolCreateSimple(capacity) cxMempoolCreate(capacity, CX_MEMPOOL_TYPE_SIMPLE) /** * Sets the destructor function for a specific allocated memory object. * + * If the type of memory pool is not #CX_MEMPOOL_TYPE_SIMPLE, the behavior is undefined. * If the memory is not managed by a UCX memory pool, the behavior is undefined. * The destructor MUST NOT free the memory. * @@ -123,10 +214,29 @@ ); /** + * Sets the destructor function for a specific allocated memory object. + * + * If the type of memory pool is not #CX_MEMPOOL_TYPE_ADVANCED, the behavior is undefined. + * If the memory is not managed by a UCX memory pool, the behavior is undefined. + * The destructor MUST NOT free the memory. + * + * @param memory the object allocated in the pool + * @param fnc the destructor function + * @param data additional data for the destructor function + */ +cx_attr_nonnull +cx_attr_export +void cxMempoolSetDestructor2( + void *memory, + cx_destructor_func2 fnc, + void *data +); + +/** * Removes the destructor function for a specific allocated memory object. * + * If the type of memory pool is not #CX_MEMPOOL_TYPE_SIMPLE, the behavior is undefined. * If the memory is not managed by a UCX memory pool, the behavior is undefined. - * The destructor MUST NOT free the memory. * * @param memory the object allocated in the pool */ @@ -135,12 +245,25 @@ void cxMempoolRemoveDestructor(void *memory); /** + * Removes the destructor function for a specific allocated memory object. + * + * If the type of memory pool is not #CX_MEMPOOL_TYPE_ADVANCED, the behavior is undefined. + * If the memory is not managed by a UCX memory pool, the behavior is undefined. + * + * @param memory the object allocated in the pool + */ +cx_attr_nonnull +cx_attr_export +void cxMempoolRemoveDestructor2(void *memory); + +/** * Registers foreign memory with this pool. * * The destructor, in contrast to memory allocated by the pool, MUST free the memory. + * This function can be used with any pool of any type, since destructors for registered memory + * are entirely independent of the pool's memory management. * - * A small portion of memory will be allocated to register the information in the pool. - * If that allocation fails, this function will return non-zero. + * The destructor for the registered memory will be called after all pooled items have been freed. * * @param pool the pool * @param memory the object to register (MUST NOT be already allocated in the pool) @@ -156,6 +279,35 @@ cx_destructor_func destr ); + +/** + * Registers foreign memory with this pool. + * + * The destructor, in contrast to memory allocated by the pool, MUST free the memory. + * This function can be used with any pool of any type, since destructors for registered memory + * are entirely independent of the pool's memory management. + * + * The destructor for the registered memory will be called after all pooled items have been freed. + * + * @attention The data pointer MUST NOT be @c NULL. + * If you wish to register a destructor without additional data, use cxMempoolRegister(). + * + * @param pool the pool + * @param memory the object to register (MUST NOT be already allocated in the pool) + * @param destr the destructor function + * @param data additional data for the destructor function + * @retval zero success + * @retval non-zero failure + */ +cx_attr_nonnull +cx_attr_export +int cxMempoolRegister2( + CxMempool *pool, + void *memory, + cx_destructor_func2 destr, + void *data +); + /** * Transfers all the memory managed by one pool to another. * @@ -164,10 +316,12 @@ * * The source pool will get a completely new allocator and can be reused or destroyed afterward. * + * This function fails when the destination pool has a different type than the source pool. + * * @param source the pool to move the memory from * @param dest the pool where to transfer the memory to * @retval zero success - * @retval non-zero failure + * @retval non-zero allocation failure or incompatible pools */ cx_attr_nonnull cx_attr_export @@ -179,8 +333,7 @@ /** * Transfers an object from one pool to another. * - * You can only transfer objects that have been allocated by this pool. - * Objects that have been registered with cxMempoolRegister() cannot be transferred this way. + * This function fails when the destination pool has a different type than the source pool. * * @attention If the object maintains a reference to the pool's allocator, * you must make sure to update that reference to the allocator of the destination pool. @@ -189,7 +342,7 @@ * @param dest the pool where to transfer the memory to * @param obj pointer to the object that shall be transferred * @retval zero success - * @retval non-zero failure or object was not found in the source pool + * @retval non-zero failure, or the object was not found in the source pool, or the pools are incompatible */ cx_attr_nonnull cx_attr_export diff -r 7be10b57f658 -r deccdb82f24e src/mempool.c --- a/src/mempool.c Thu May 22 16:23:55 2025 +0200 +++ b/src/mempool.c Thu May 22 16:25:32 2025 +0200 @@ -31,13 +31,6 @@ #include #include -struct cx_mempool_memory_s { - /** The destructor. */ - cx_destructor_func destructor; - /** The actual memory. */ - char c[]; -}; - static int cx_mempool_ensure_capacity( struct cx_mempool_s *pool, size_t needed_capacity @@ -46,20 +39,39 @@ size_t newcap = pool->capacity >= 1000 ? pool->capacity + 1000 : pool->capacity * 2; size_t newmsize; - if (pool->capacity > newcap || cx_szmul(newcap, - sizeof(struct cx_mempool_memory_s*), &newmsize)) { + if (pool->capacity > newcap + || cx_szmul(newcap, sizeof(void*), &newmsize)) { errno = EOVERFLOW; return 1; } - struct cx_mempool_memory_s **newdata = cxRealloc( - cxDefaultAllocator, pool->data, newmsize); + void **newdata = cxReallocDefault(pool->data, newmsize); if (newdata == NULL) return 1; pool->data = newdata; pool->capacity = newcap; return 0; } -static void *cx_mempool_malloc( +static int cx_mempool_ensure_registered_capacity( + struct cx_mempool_s *pool, + size_t needed_capacity +) { + if (needed_capacity <= pool->registered_capacity) return 0; + // we do not expect so many registrations + size_t newcap = pool->registered_capacity + 8; + size_t newmsize; + if (pool->registered_capacity > newcap || cx_szmul(newcap, + sizeof(struct cx_mempool_foreign_memory_s), &newmsize)) { + errno = EOVERFLOW; + return 1; + } + void *newdata = cxReallocDefault(pool->registered, newmsize); + if (newdata == NULL) return 1; + pool->registered = newdata; + pool->registered_capacity = newcap; + return 0; +} + +static void *cx_mempool_malloc_simple( void *p, size_t n ) { @@ -69,18 +81,17 @@ return NULL; } - struct cx_mempool_memory_s *mem = cxMalloc( - cxDefaultAllocator, sizeof(cx_destructor_func) + n); + struct cx_mempool_memory_s *mem = + cxMallocDefault(sizeof(struct cx_mempool_memory_s) + n); if (mem == NULL) return NULL; - - mem->destructor = pool->auto_destr; + mem->destructor = NULL; pool->data[pool->size] = mem; pool->size++; return mem->c; } -static void *cx_mempool_calloc( +static void *cx_mempool_calloc_simple( void *p, size_t nelem, size_t elsize @@ -90,29 +101,31 @@ errno = EOVERFLOW; return NULL; } - void *ptr = cx_mempool_malloc(p, msz); + void *ptr = cx_mempool_malloc_simple(p, msz); if (ptr == NULL) return NULL; memset(ptr, 0, nelem * elsize); return ptr; } -static void *cx_mempool_realloc( +static void *cx_mempool_realloc_simple( void *p, void *ptr, size_t n ) { struct cx_mempool_s *pool = p; - struct cx_mempool_memory_s *mem, *newm; - mem = (struct cx_mempool_memory_s*)(((char *) ptr) - sizeof(cx_destructor_func)); - newm = cxReallocDefault(mem, n + sizeof(cx_destructor_func)); + const unsigned overhead = sizeof(struct cx_mempool_memory_s); + struct cx_mempool_memory_s *mem = + (void *) (((char *) ptr) - overhead); + struct cx_mempool_memory_s *newm = + cxReallocDefault(mem, n + overhead); if (newm == NULL) return NULL; if (mem != newm) { for (size_t i = 0; i < pool->size; i++) { if (pool->data[i] == mem) { pool->data[i] = newm; - return ((char*)newm) + sizeof(cx_destructor_func); + return ((char*)newm) + overhead; } } abort(); // LCOV_EXCL_LINE @@ -121,21 +134,27 @@ } } -static void cx_mempool_free( +static void cx_mempool_free_simple( void *p, void *ptr ) { if (!ptr) return; struct cx_mempool_s *pool = p; - struct cx_mempool_memory_s *mem = (struct cx_mempool_memory_s *) - ((char *) ptr - sizeof(cx_destructor_func)); + struct cx_mempool_memory_s *mem = + (void*) ((char *) ptr - sizeof(struct cx_mempool_memory_s)); for (size_t i = 0; i < pool->size; i++) { if (mem == pool->data[i]) { if (mem->destructor) { mem->destructor(mem->c); } + if (pool->destr) { + pool->destr(mem->c); + } + if (pool->destr2) { + pool->destr2(pool->destr2_data, mem->c); + } cxFreeDefault(mem); size_t last_index = pool->size - 1; if (i != last_index) { @@ -149,17 +168,54 @@ abort(); // LCOV_EXCL_LINE } -void cxMempoolFree(CxMempool *pool) { - if (pool == NULL) return; - struct cx_mempool_memory_s *mem; +static void cx_mempool_free_all_simple(const struct cx_mempool_s *pool) { + const bool has_destr = pool->destr; + const bool has_destr2 = pool->destr2; for (size_t i = 0; i < pool->size; i++) { - mem = pool->data[i]; + struct cx_mempool_memory_s *mem = pool->data[i]; if (mem->destructor) { mem->destructor(mem->c); } + if (has_destr) { + pool->destr(mem->c); + } + if (has_destr2) { + pool->destr2(pool->destr2_data, mem->c); + } cxFreeDefault(mem); } +} + +static void cx_mempool_free_foreign(const struct cx_mempool_s *pool) { + for (size_t i = 0; i < pool->registered_size; i++) { + struct cx_mempool_foreign_memory_s info = pool->registered[i]; + if (info.destr2_data == NULL) { + if (info.destr) { + info.destr(info.mem); + } + } else { + info.destr2(info.destr2_data, info.mem); + } + } +} + +static cx_allocator_class cx_mempool_simple_allocator_class = { + cx_mempool_malloc_simple, + cx_mempool_realloc_simple, + cx_mempool_calloc_simple, + cx_mempool_free_simple +}; + +void cxMempoolFree(CxMempool *pool) { + if (pool == NULL) return; + if (pool->allocator->cl == &cx_mempool_simple_allocator_class) { + cx_mempool_free_all_simple(pool); + } else { + // TODO: implement + } + cx_mempool_free_foreign(pool); cxFreeDefault(pool->data); + cxFreeDefault(pool->registered); cxFreeDefault((void*) pool->allocator); cxFreeDefault(pool); } @@ -175,64 +231,56 @@ *(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = NULL; } -struct cx_mempool_foreign_mem_s { - cx_destructor_func destr; - void* mem; -}; - -static void cx_mempool_destr_foreign_mem(void* ptr) { - struct cx_mempool_foreign_mem_s *fm = ptr; - fm->destr(fm->mem); -} - int cxMempoolRegister( CxMempool *pool, void *memory, cx_destructor_func destr ) { - struct cx_mempool_foreign_mem_s *fm = cx_mempool_malloc( - pool, - sizeof(struct cx_mempool_foreign_mem_s) - ); - if (fm == NULL) return 1; + if (cx_mempool_ensure_registered_capacity(pool, pool->registered_size + 1)) { + return 1; // LCOV_EXCL_LINE + } - fm->mem = memory; - fm->destr = destr; - *(cx_destructor_func *) ((char *) fm - sizeof(cx_destructor_func)) = cx_mempool_destr_foreign_mem; + pool->registered[pool->registered_size++] = + (struct cx_mempool_foreign_memory_s) { + .mem = memory, + .destr = destr, + .destr2_data = NULL + }; return 0; } -static cx_allocator_class cx_mempool_allocator_class = { - cx_mempool_malloc, - cx_mempool_realloc, - cx_mempool_calloc, - cx_mempool_free -}; - CxMempool *cxMempoolCreate( size_t capacity, - cx_destructor_func destr + enum cx_mempool_type type ) { + if (capacity == 0) capacity = 16; size_t poolsize; - if (cx_szmul(capacity, sizeof(struct cx_mempool_memory_s*), &poolsize)) { + if (cx_szmul(capacity, sizeof(void*), &poolsize)) { errno = EOVERFLOW; return NULL; } - struct cx_mempool_s *pool = - cxMallocDefault(sizeof(struct cx_mempool_s)); - if (pool == NULL) return NULL; - CxAllocator *provided_allocator = cxMallocDefault(sizeof(CxAllocator)); if (provided_allocator == NULL) { // LCOV_EXCL_START - cxFreeDefault(pool); return NULL; } // LCOV_EXCL_STOP - provided_allocator->cl = &cx_mempool_allocator_class; + + CxMempool *pool = cxCallocDefault(1, sizeof(CxMempool)); + if (pool == NULL) { + cxFreeDefault(provided_allocator); + return NULL; + } + provided_allocator->data = pool; - pool->allocator = provided_allocator; + if (type == CX_MEMPOOL_TYPE_SIMPLE) { + provided_allocator->cl = &cx_mempool_simple_allocator_class; + } else if (type == CX_MEMPOOL_TYPE_ADVANCED) { + // TODO: implement + } else { + // TODO: implement + } pool->data = cxMallocDefault(poolsize); if (pool->data == NULL) { // LCOV_EXCL_START @@ -243,7 +291,6 @@ pool->size = 0; pool->capacity = capacity; - pool->auto_destr = destr; return pool; } @@ -256,11 +303,16 @@ CxMempool *source, CxMempool *dest ) { - // safety check + // safety checks if (source == dest) return 1; + if (source->allocator->cl != dest->allocator->cl) return 1; // ensure enough capacity in the destination pool - if (cx_mempool_ensure_capacity(dest, dest->size + source->size + 1)) { + if (cx_mempool_ensure_capacity(dest, dest->size + source->size)) { + return 1; // LCOV_EXCL_LINE + } + if (cx_mempool_ensure_registered_capacity(dest, + dest->registered_size + source->registered_size)) { return 1; // LCOV_EXCL_LINE } @@ -269,23 +321,32 @@ if (new_source_allocator == NULL) { // LCOV_EXCL_START return 1; } // LCOV_EXCL_STOP - new_source_allocator->cl = &cx_mempool_allocator_class; + new_source_allocator->cl = source->allocator->cl; new_source_allocator->data = source; // transfer all the data - memcpy(&dest->data[dest->size], source->data, sizeof(source->data[0])*source->size); + memcpy(&dest->data[dest->size], source->data, sizeof(void*)*source->size); dest->size += source->size; + // transfer all registered memory + memcpy(&dest->registered[dest->registered_size], source->registered, + sizeof(struct cx_mempool_foreign_memory_s) * source->size); + dest->registered_size += source->registered_size; + // register the old allocator with the new pool // we have to remove const-ness for this, but that's okay here CxAllocator *transferred_allocator = (CxAllocator*) source->allocator; transferred_allocator->data = dest; - cxMempoolRegister(dest, transferred_allocator, cx_mempool_free_transferred_allocator); + cxMempoolRegister(dest, transferred_allocator, + cx_mempool_free_transferred_allocator); // prepare the source pool for re-use source->allocator = new_source_allocator; - memset(source->data, 0, source->size * sizeof(source->data[0])); + memset(source->data, 0, source->size * sizeof(void*)); + memset(source->registered, 0, + source->registered_size * sizeof(struct cx_mempool_foreign_memory_s)); source->size = 0; + source->registered_size = 0; return 0; } diff -r 7be10b57f658 -r deccdb82f24e tests/test_mempool.c --- a/tests/test_mempool.c Thu May 22 16:23:55 2025 +0200 +++ b/tests/test_mempool.c Thu May 22 16:25:32 2025 +0200 @@ -34,7 +34,9 @@ CX_TEST(test_mempool_create) { CxMempool *pool = cxMempoolCreateSimple(16); CX_TEST_DO { - CX_TEST_ASSERT(pool->auto_destr == NULL); + CX_TEST_ASSERT(pool->destr == NULL); + CX_TEST_ASSERT(pool->destr2 == NULL); + CX_TEST_ASSERT(pool->destr2_data == NULL); CX_TEST_ASSERT(pool->allocator != NULL); CX_TEST_ASSERT(pool->allocator->cl != NULL); CX_TEST_ASSERT(pool->allocator->data == pool); @@ -86,9 +88,10 @@ } CX_TEST(test_mempool_realloc) { - CxMempool *pool = cxMempoolCreate(4, test_mempool_destructor); + CxMempool *pool = cxMempoolCreateSimple(4); + cxMempoolGlobalDestructor(pool, test_mempool_destructor); CX_TEST_DO { - CX_TEST_ASSERT(pool->auto_destr == test_mempool_destructor); + CX_TEST_ASSERT(pool->destr == test_mempool_destructor); int *data = cxMalloc(pool->allocator, sizeof(int)); *data = 13; @@ -168,19 +171,19 @@ CxMempool *src = cxMempoolCreateSimple(4); CxMempool *dest = cxMempoolCreateSimple(4); CX_TEST_DO { + // allocate the first object + int *c = cxMalloc(src->allocator, sizeof(int)); + // allocate the second object + c = cxMalloc(src->allocator, sizeof(int)); // check that the destructor functions are also transferred - src->auto_destr = test_mempool_destructor; - - // allocate first object - int *c = cxMalloc(src->allocator, sizeof(int)); - // allocate second object - c = cxMalloc(src->allocator, sizeof(int)); + cxMempoolSetDestructor(c, test_mempool_destructor); // register foreign object c = malloc(sizeof(int)); cxMempoolRegister(src, c, test_mempool_destructor); // check source pool - CX_TEST_ASSERT(src->size == 3); + CX_TEST_ASSERT(src->size == 2); + CX_TEST_ASSERT(src->registered_size == 1); const CxAllocator *old_allocator = src->allocator; CX_TEST_ASSERT(old_allocator->data == src); @@ -190,7 +193,9 @@ // check transfer CX_TEST_ASSERT(src->size == 0); - CX_TEST_ASSERT(dest->size == 4); // 3 objects + the old allocator + CX_TEST_ASSERT(dest->size == 2); + CX_TEST_ASSERT(src->registered_size == 0); + CX_TEST_ASSERT(dest->registered_size == 2); // 1 object + old allocator CX_TEST_ASSERT(src->allocator != old_allocator); CX_TEST_ASSERT(old_allocator->data == dest); @@ -204,9 +209,9 @@ CX_TEST_ASSERT(result != 0); // verify that destroying new pool calls the destructors - // but only three times (the old allocator has a different destructor) + // but only two times (the old allocator has a different destructor) cxMempoolFree(dest); - CX_TEST_ASSERT(test_mempool_destructor_called == 3); + CX_TEST_ASSERT(test_mempool_destructor_called == 2); // free the foreign object free(c); @@ -221,30 +226,32 @@ b = cxMalloc(src->allocator, sizeof(int)); int *c = malloc(sizeof(int)); cxMempoolRegister(src, c, free); + CX_TEST_ASSERT(src->size == 2); + CX_TEST_ASSERT(src->registered_size == 1); - CX_TEST_ASSERT(src->size == 3); int result = cxMempoolTransferObject(src, dest, b); CX_TEST_ASSERT(result == 0); - CX_TEST_ASSERT(src->size == 2); + CX_TEST_ASSERT(src->size == 1); CX_TEST_ASSERT(dest->size == 1); result = cxMempoolTransferObject(src, dest, b); CX_TEST_ASSERT(result != 0); - CX_TEST_ASSERT(src->size == 2); + CX_TEST_ASSERT(src->size == 1); CX_TEST_ASSERT(dest->size == 1); + // cannot transfer foreign memory this way result = cxMempoolTransferObject(src, dest, c); CX_TEST_ASSERT(result != 0); - CX_TEST_ASSERT(src->size == 2); + CX_TEST_ASSERT(src->size == 1); CX_TEST_ASSERT(dest->size == 1); + result = cxMempoolTransferObject(dest, dest, b); CX_TEST_ASSERT(result != 0); - CX_TEST_ASSERT(src->size == 2); + CX_TEST_ASSERT(src->size == 1); CX_TEST_ASSERT(dest->size == 1); - - cxMempoolFree(src); - cxMempoolFree(dest); - // let valgrind check that everything worked } + cxMempoolFree(src); + cxMempoolFree(dest); + // let valgrind check that everything worked } CxTestSuite *cx_test_suite_mempool(void) {