C语言通用内存池开发:指针转换与free功能实现困境
Hey, let's break down what's going wrong here and fix your memory pool implementation. The core issue is that your current design separates the mem_block metadata and the actual user data into completely separate heap allocations—so when you get a data pointer back from pool_allocate, there's no way to trace it back to its corresponding mem_block. Your attempt to cast the data pointer to mem_block won't work because they're not pointing to the same memory region at all.
1. Root Cause of Your Problem
In your original code, you're doing this for each allocation:
uint8_t* data = (uint8_t*) malloc(size * sizeof(uint8_t)); allocated_blk->data = data;
This creates two disconnected chunks of memory: one for the mem_block metadata, and another for the user data. When you try to cast c2 (which points to the user data chunk) to mem_block, you're just reading random heap memory as a struct—hence the garbage pool_position values you saw.
A proper memory pool should keep metadata and user data linked in a way that lets you reverse-lookup metadata from a data pointer.
2. Fixed Implementation
We'll use a simple, common approach: embed the mem_block metadata directly before the user data in a single contiguous pool buffer. This lets us calculate the metadata address from any data pointer with basic arithmetic.
#include <stdlib.h> #include <string.h> #include <stddef.h> #include <stdint.h> #include <stdio.h> typedef enum { FALSE, TRUE } BOOL; // Metadata lives immediately BEFORE user data typedef struct mem_block { size_t block_size; BOOL is_freed; struct mem_block* next; // For tracking free blocks } mem_block; typedef struct mem_pool { uint8_t* buffer; // Single contiguous pool memory size_t pool_size; mem_block* free_list; // Linked list of available blocks } mem_pool; mem_pool *pool_init() { mem_pool *pool = malloc(sizeof(mem_pool)); if (!pool) return NULL; pool->pool_size = 128; // Allocate the entire pool buffer (holds metadata + user data) pool->buffer = malloc(pool->pool_size); if (!pool->buffer) { free(pool); return NULL; } // Initialize the first free block (covers the entire pool minus metadata) mem_block* initial_block = (mem_block*)pool->buffer; initial_block->block_size = pool->pool_size - sizeof(mem_block); initial_block->is_freed = TRUE; initial_block->next = NULL; pool->free_list = initial_block; return pool; } void *pool_allocate(mem_pool *pool, size_t size) { if (!pool || size == 0) return NULL; // Align size to avoid alignment issues (good practice for primitive types) size = (size + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1); // Search free list for a block large enough mem_block** current = &pool->free_list; while (*current) { mem_block* block = *current; if (block->is_freed && block->block_size >= size) { // Split the block if there's leftover space for another metadata block if (block->block_size - size > sizeof(mem_block)) { mem_block* new_block = (mem_block*)((uint8_t*)block + sizeof(mem_block) + size); new_block->block_size = block->block_size - size - sizeof(mem_block); new_block->is_freed = TRUE; new_block->next = block->next; block->block_size = size; block->next = new_block; } block->is_freed = FALSE; // Return the data region right after the metadata return (uint8_t*)block + sizeof(mem_block); } current = &block->next; } printf("Pool is out of memory\n"); return NULL; } void pool_free(mem_pool *pool, void *data) { if (!pool || !data) return; // Calculate metadata address by subtracting the size of mem_block from data pointer mem_block* block = (mem_block*)((uint8_t*)data - sizeof(mem_block)); // Sanity check: make sure the pointer is within our pool buffer if (block < (mem_block*)pool->buffer || (uint8_t*)block >= pool->buffer + pool->pool_size) { printf("Invalid pointer to free\n"); return; } block->is_freed = TRUE; // Optional: Coalesce adjacent free blocks to reduce fragmentation mem_block* current = pool->free_list; mem_block* prev = NULL; while (current && current < block) { prev = current; current = current->next; } // Merge with previous free block if adjacent if (prev && prev->is_freed && (uint8_t*)prev + sizeof(mem_block) + prev->block_size == (uint8_t*)block) { prev->block_size += sizeof(mem_block) + block->block_size; prev->next = block->next; block = prev; } // Merge with next free block if adjacent if (block->next && block->next->is_freed && (uint8_t*)block + sizeof(mem_block) + block->block_size == (uint8_t*)block->next) { block->block_size += sizeof(mem_block) + block->next->block_size; block->next = block->next->next; } // Update free list if (!prev) { pool->free_list = block; } else { prev->next = block; } } typedef struct complex { double i; double r; } complex; int main() { mem_pool *GLOBAL_POOL = pool_init(); if (!GLOBAL_POOL) { printf("Failed to initialize pool\n"); return -1; } complex *c1 = pool_allocate(GLOBAL_POOL, sizeof(complex)); if (c1) { c1->r = 1.0; c1->i = 2.0; printf("Value is (%f + %fi)\n", c1->r, c1->i); } complex *c2 = pool_allocate(GLOBAL_POOL, sizeof(complex)); if (c2) { c2->r = 2.0; c2->i = 3.0; printf("Value is (%f + %fi)\n", c2->r, c2->i); } // Test freeing and reusing space pool_free(GLOBAL_POOL, c2); printf("Freed c2\n"); complex *c3 = pool_allocate(GLOBAL_POOL, sizeof(complex)); if (c3) { c3->r = 3.0; c3->i = 4.0; printf("Value is (%f + %fi)\n", c3->r, c3->i); printf("c3 uses c2's old address? %s\n", c3 == c2 ? "Yes" : "No"); } // Cleanup the pool free(GLOBAL_POOL->buffer); free(GLOBAL_POOL); return 0; }
3. Key Improvements Explained
- Linked Metadata: Each data pointer is directly preceded by its
mem_blockmetadata, so we can always get the metadata by subtractingsizeof(mem_block)from the data pointer. - Single Pool Buffer: No more scattered
malloccalls—all memory lives in one contiguous block, which is the point of a memory pool. - Free List: We track available blocks with a linked list, making allocation and deallocation efficient even after multiple frees.
- Fragmentation Reduction: The
pool_freefunction merges adjacent free blocks to keep large contiguous regions available.
4. Next Steps for Dynamic Pool Sizing
To make your pool grow dynamically:
- Add a
pool_resizefunction that allocates a larger buffer, copies existing data/metadata to it, and updates the free list. - Alternatively, use a linked list of multiple pool buffers—when one is full, allocate a new buffer and add it to the list.
内容的提问来源于stack exchange,提问作者meguli

