如何在C++中实现一个自定义内存池?

在高性能系统或者嵌入式开发中,频繁的 new/delete 会产生大量的堆碎片和系统调用,导致内存分配成为瓶颈。通过实现一个自定义内存池(Memory Pool),可以将大块内存按需划分,显著提升分配速度并降低碎片。下面给出一个简单而实用的内存池实现思路,并附上完整的示例代码。

1. 设计思路

  1. 预分配大块
    在构造函数中一次性从系统堆中申请一大块内存(比如 1 MiB)。
  2. 链表管理
    将这块内存划分成若干固定大小的块(Block),并用单向链表链接未使用的块。
  3. 分配/释放
    • allocate():返回链表头节点,并把链表头指向下一个可用块。
    • deallocate(ptr):将 ptr 重新插入链表头。
  4. 扩容
    当链表为空时(无可用块),可以再次申请一块新的大块,继续扩展池。
  5. 线程安全
    为了演示,使用 std::mutex 保护分配/释放操作;如果需要更高并发,可考虑无锁实现或 per‑thread 子池。

2. 关键参数

  • BLOCK_SIZE:单个对象的大小(包括对齐)。
  • POOL_SIZE:每次申请的大块大小,建议为 BLOCK_SIZE * NUM_BLOCKS_PER_POOL
  • NUM_BLOCKS_PER_POOL:每块大内存中可分配的块数。

3. 示例代码

#include <cstddef>
#include <cstdlib>
#include <mutex>
#include <iostream>
#include <vector>
#include <cassert>

class SimpleMemoryPool
{
public:
    explicit SimpleMemoryPool(std::size_t blockSize = 64,
                              std::size_t blocksPerPool = 1024)
        : blockSize_(blockSize)
        , blocksPerPool_(blocksPerPool)
        , freeList_(nullptr)
    {
        // 预分配第一个池
        allocatePool();
    }

    ~SimpleMemoryPool()
    {
        for (auto pool : pools_)
            std::free(pool);
    }

    // 禁止拷贝
    SimpleMemoryPool(const SimpleMemoryPool&) = delete;
    SimpleMemoryPool& operator=(const SimpleMemoryPool&) = delete;

    void* allocate()
    {
        std::lock_guard<std::mutex> lock(mutex_);
        if (!freeList_)
            allocatePool();          // 再无空闲块时扩容

        void* ptr = freeList_;
        freeList_ = reinterpret_cast<Block*>(freeList_->next);
        return ptr;
    }

    void deallocate(void* ptr)
    {
        std::lock_guard<std::mutex> lock(mutex_);
        auto block = reinterpret_cast<Block*>(ptr);
        block->next = freeList_;
        freeList_ = block;
    }

private:
    struct Block
    {
        Block* next;
    };

    void allocatePool()
    {
        std::size_t poolSize = blockSize_ * blocksPerPool_;
        void* pool = std::malloc(poolSize);
        if (!pool)
            throw std::bad_alloc();

        pools_.push_back(pool);

        // 将新池划分成块,并插入链表
        char* p = static_cast<char*>(pool);
        for (std::size_t i = 0; i < blocksPerPool_; ++i)
        {
            auto block = reinterpret_cast<Block*>(p + i * blockSize_);
            block->next = freeList_;
            freeList_ = block;
        }
    }

    const std::size_t blockSize_;
    const std::size_t blocksPerPool_;
    std::vector<void*> pools_;
    Block* freeList_;
    std::mutex mutex_;
};

// 测试
struct TestStruct
{
    int a[4];
    double b;
};

int main()
{
    SimpleMemoryPool pool(sizeof(TestStruct));

    // 分配 10 个 TestStruct
    std::vector<void*> ptrs;
    for (int i = 0; i < 10; ++i)
    {
        void* p = pool.allocate();
        ptrs.push_back(p);
        // 在内存中构造对象
        new (p) TestStruct{ {i, i+1, i+2, i+3}, 3.14 };
    }

    // 使用后析构并归还
    for (void* p : ptrs)
    {
        static_cast<TestStruct*>(p)->~TestStruct();
        pool.deallocate(p);
    }

    std::cout << "Memory pool demo finished.\n";
    return 0;
}

代码要点

  • 对齐:默认 malloc 具备足够的对齐,若需要特殊对齐可使用 std::aligned_alloc
  • 构造/析构allocate() 只返回裸内存,若需要对象构造,请使用 placement newdeallocate() 只处理内存回收,析构需要自己调用。
  • 性能:分配/释放时仅需一次指针操作和一次锁操作,远快于系统堆。
  • 可扩展:可以在 allocatePool() 里加入自适应策略(例如按需扩大 blocksPerPool_)。

4. 小结

自定义内存池是提升 C++ 程序性能的一大利器。通过上述实现,你可以快速集成一个基础池,并根据需求进一步优化(如对象复用、线程局部池、分块大小动态调整等)。在对实时性或内存碎片有严苛要求的项目中,使用内存池往往能带来显著收益。

发表评论