You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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_block metadata, so we can always get the metadata by subtracting sizeof(mem_block) from the data pointer.
  • Single Pool Buffer: No more scattered malloc calls—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_free function merges adjacent free blocks to keep large contiguous regions available.

4. Next Steps for Dynamic Pool Sizing

To make your pool grow dynamically:

  1. Add a pool_resize function that allocates a larger buffer, copies existing data/metadata to it, and updates the free list.
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:06:23