C语言动态内存池完全指南

频繁调用 malloc/free 会导致内存碎片化,影响程序性能。内存池(Memory Pool)通过预分配大块内存并在池内部分配,显著减少系统调用碎片化问题。本文详细介绍内存池的原理与实现。

为什么需要内存池

标准的 malloc/free 存在以下问题:

  • 系统调用开销:每次分配都可能触发 brk 或 mmap 系统调用
  • 内存碎片:频繁分配释放后,内存空间呈碎片化
  • 缓存失效:可能导致 CPU 缓存命中率下降

适用场景

内存池适合需要频繁分配释放固定大小对象的场景,如网络服务器、数据库连接池、对象池等。

简单固定块内存池

首先实现一个固定大小的内存池,每次分配固定大小的内存块:

#include 
#include 
#include 

#define POOL_BLOCK_SIZE 4096    /* 每块 4KB */
#define BLOCK_SIZE 64          /* 每个对象 64 字节 */

typedef struct Block {
    struct Block *next;
    char data[1];  /* 可变长度数据 */
} Block;

typedef struct MemPool {
    Block *used_list;
    Block *free_list;
    size_t block_size;
} MemPool;

void *pool_init(size_t size) {
    MemPool *pool = malloc(sizeof(MemPool));
    if (!pool) return NULL;

    pool->block_size = size;
    pool->free_list = NULL;
    pool->used_list = NULL;

    /* 预分配初始块 */
    for (int i = 0; i 16; i++) {
        void *ptr = pool_alloc(pool);
        if (ptr) pool_free(pool, ptr);
    }

    return pool;
}

void *pool_alloc(MemPool *pool) {
    if (!pool->free_list) {
        /* 分配新块 */
        Block *block = malloc(sizeof(Block) + pool->block_size - 1);
        if (!block) return NULL;

        /* 添加到使用列表 */
        block->next = pool->used_list;
        pool->used_list = block;
        return block->data;
    }

    /* 从空闲列表取出 */
    Block *block = pool->free_list;
    pool->free_list = block->next;

    /* 添加到使用列表 */
    block->next = pool->used_list;
    pool->used_list = block;

    return block->data;
}

void pool_free(MemPool *pool, void *ptr) {
    if (!ptr) return;

    /* 找到对应的 Block */
    Block *block = (char *)ptr - offsetof(Block, data);

    /* 从使用列表移除 */
    Block **prev = &pool->used_list;
    while (*prev && *prev != block) prev = &(*prev)->next;

    if (!*prev) return;

    *prev = block->next;

    /* 添加到空闲列表 */
    block->next = pool->free_list;
    pool->free_list = block;
}

slab 分配器

slab 分配器为不同大小的对象维护不同的缓存池,进一步提高内存利用效率:

#include 

#define SLAB_SIZE 4096
#define MAX_SLABS 32

typedef struct Slab {
    void *memory;
    size_t object_size;
    size_t count;
    size_t free_count;
    void **free_list;
    struct Slab *next;
} Slab;

typedef struct SlabAllocator {
    Slab *slabs[MAX_SLABS];
    size_t object_size;
} SlabAllocator;

void *slab_alloc(SlabAllocator *alloc) {
    for (Slab **s = alloc->slabs; *s; s = &(*s)->next) {
        if ((*s)->free_count > 0) {
            void *ptr = (*s)->free_list;
            (*s)->free_list = (void *)*(*s)->free_list;
            (*s)->free_count--;
            return ptr;
        }
    }

    /* 创建新的 slab */
    Slab *slab = malloc(sizeof(Slab));
    slab->memory = malloc(SLAB_SIZE);
    slab->object_size = alloc->object_size;
    slab->count = SLAB_SIZE / alloc->object_size;
    slab->free_count = slab->count;

    /* 初始化空闲链表 */
    slab->free_list = malloc(sizeof(void *) * slab->count);
    char *p = slab->memory;
    for (size_t i = 0; i < slab->count - 1; i++) {
        slab->free_list[i] = p;
        p += alloc->object_size;
    }
    slab->free_list[slab->count - 1] = NULL;

    slab->next = alloc->slabs[0];
    alloc->slabs[0] = slab;

    return slab->memory;
}

TLSF 分配器

TLSF(Two Level Segregate Fit)是一个高效的内存分配器,适合实时系统:

/* TLSF 核心思路:两级位图快速定位空闲块 */
/*
 * 第一级:将内存按 2^r 大小分类(如 32, 64, 128...)
 * 第二级:将每类再细分多个小块
 * 使用位图快速查找第一个非空列表
 */

#define FLI 6        /* 第一级数量:2^6 = 64 ~ 2^31 */
#define SLI 4        /* 第二级每级 4 个块 */

typedef struct TLSF {
    void *heap;
    unsigned int fl_bitmap;  /* 第一级位图 */
    unsigned int sl_bitmap[FLI];  /* 第二级位图 */
    void *free_list[FLI][SLI];  /* 空闲块链表 */
} TLSF;

/* 快速找到第一个非空块 */
int ffs(unsigned int x) {
    if (!x) return -1;
    int i = 0;
    while (!(x & 1)) { x >>= 1; i++; }
    return i;
}

Arena 内存分配器

Arena 分配器一次性分配大块内存,所有分配共享同一个生命周期,适合临时对象:

typedef struct Arena {
    char *base;
    size_t offset;
    size_t capacity;
} Arena;

Arena *arena_create(size_t capacity) {
    Arena *arena = malloc(sizeof(Arena));
    arena->base = malloc(capacity);
    arena->offset = 0;
    arena->capacity = capacity;
    return arena;
}

void *arena_alloc(Arena *arena, size_t size) {
    /* 对齐到 8 字节 */
    size_t align = (size + 7) & ~7;
    if (arena->offset + align > arena->capacity) return NULL;

    void *ptr = arena->base + arena->offset;
    arena->offset += align;
    return ptr;
}

void arena_reset(Arena *arena) {
    arena->offset = 0;  /* 快速重置,所有内存可复用 */
}

void arena_destroy(Arena *arena) {
    free(arena->base);
    free(arena);
}

Arena 适用场景

解析 JSON/XML、编译器的符号表构建、短期存在的临时数据等。分配的对象通常一起释放,不需要单独释放。

总结

  • 固定块内存池:适合对象大小固定的场景,实现简单
  • slab 分配器:支持多种大小,Linux 内核使用此方案
  • TLSF:O(1) 分配时间,适合实时系统
  • Arena:批量分配和释放,适合临时对象

根据具体场景选择合适的内存池实现,可以显著提升程序性能和内存利用效率。