一个C++的内存池和内存管理的实现(二)

本文介绍了一个单模内存池管理类的设计与实现细节,包括内存的分配、释放、重用等功能,并提供了线程安全保证。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

接下来我们定义一个单模的类,用来管理内存的申请、释放、重用等

    class MemPool
    {

    private:
        MemPool();

        bool AllocPage();  // 分配一个页
        void* AllocLarge(int nSize, const char* const szFileName, int nLine, bool bIsArray);  // 分配一个大内存(>2048)

        bool IsInBlockPool(Prefix* pPrefix) const { return (pPrefix->d.pPostfix->nBlockPoolIndex >= 0); };  // 判断该内存块是否在某个尺寸的链表中等待被重用

    public:
        ~MemPool();

        static MemPool& GetInstance();  // 单模实例

        void* Alloc(int nSize, const char* const szFileName, int nLine, bool bIsArray);  // 分配内存的入口函数
        void Free(void* pMem);  // 释放内存

        void AddCleanup(CleanupHandler pHandler, void* pParam);  // 添加某个清理函数,后面会举例介绍它的用处。

    private:
        MemPageHeader* m_pFirstPage;  // 指向池中第一个页
        Prefix* m_pLarge;  // 指向大内存块链表
        Prefix* m_ppBlockPools[20];  // 各种尺寸内存块的链表池。最多20种不同尺寸
        int m_nBlocksAllocated[20];  // 记录每种尺寸的内存块的已分配个数

        struct Cleanup {
            CleanupHandler pHandler;
            void *pParam;
            Cleanup* pNext;
        } *m_pCleanups;  // 清理函数的链表

        static MemPool m_heap;  // 单模实例
        CriticalSection m_cs;  // 用来保证线程安全。这是个自定义的类,封装了EnterCriticalSection等函数,忽略不影响阅读

    };

其中涉及到的清理函数的定义如下。暂时可以不用管它,以后会介绍。

typedef void (*CleanupHandler)(void *pParam);

下面我们来看具体的实现。首先是这个单模类的构造函数:

MemPool::MemPool()
{
    m_pFirstPage = NULL;  // 起始时没有从系统申请任何页

    m_pLarge = new Prefix;  // 为大内存块的链表生成一个头,这个头元素只是用来简化链表的元素删除操作,没有其他意义
    memset(m_pLarge, 0, sizeof(Prefix));

    m_pCleanups = NULL;  // 起始时没有注册任何清理函数(清理函数的作用后面再讲)

    for (int i = 0; i < 20; i ++)
    {
        m_ppBlockPools[i] = new Prefix;  // 为各尺寸的内存池链表生成一个头,这个头元素只是用来简化链表的元素删除操作,没有其他意义
        memset(m_ppBlockPools[i], 0, sizeof(Prefix));
    }
    memset(m_nBlocksAllocated, 0, 20 * sizeof(int));
}

和析构函数

MemPool::~MemPool()
{
    // 调用所有清理函数做清理。具体左右后面举例讲。
    for (Cleanup *p = m_pCleanups; p != NULL; p = p->pNext)
    {
        if (p->pHandler)
            p->pHandler(p->pParam);
    }
    // 释放所有清理函数相关结构
    while (m_pCleanups != NULL)
    {
        Cleanup *p = m_pCleanups;
        m_pCleanups = m_pCleanups->pNext;
        delete p;
    }

    // 释放所有页
    while (m_pFirstPage)
    {
        MemPageHeader *p = m_pFirstPage;
        m_pFirstPage = m_pFirstPage->d.pNext;
        _aligned_free(p);
    }

    // 释放所有大内存(>2048字节)块。
    Prefix *p = m_pLarge;
    m_pLarge = m_pLarge->d.pNext;
    delete p;  // 释放链表头
    while (m_pLarge != NULL)  // 如果软件里无内存泄露,应为NULL不会进入循环
    {
        p = m_pLarge;
        m_pLarge = m_pLarge->d.pNext;
        _aligned_free(p);
    }

    // 释放各尺寸的链表头
    for (int i = 0; i < 20; i ++)
        delete m_ppBlockPools[i];
}

单模类的实例获取,这个不用介绍了吧

MemPool& MemPool::GetInstance()
{
    return m_heap;
}

下面是我们的重头戏,内存分配的入口函数

void* MemPool::Alloc(int nSize, const char* const szFileName, int nLine, bool bIsArray)
{
    if (nSize < MAX_ALLOC_FROM_PAGE)  // <2048
    {
        int nMinPower2 = 16;  // 至少分配16字节
        int i = 0;
        for (; nMinPower2 < nSize; nMinPower2 = nMinPower2 << 1, i ++);  // 找到不小于申请尺寸的最小的2的幂,即规定的16、32、64等尺寸

        SYNCHRONIZATION();  // 线程安全宏,用到了自定义的CriticalSection,不用管
        // 查找对应尺寸的池中是否有内存块
        if (m_ppBlockPools[i]->d.pNext)  // 链表头下面有元素
        {
            Prefix *pPrefix = m_ppBlockPools[i]->d.pNext;

            // 从链表中分离该内存块
            m_ppBlockPools[i]->d.pNext = pPrefix->d.pNext;
            if (pPrefix->d.pNext)
                pPrefix->d.pNext->d.pPrev = m_ppBlockPools[i];
            pPrefix->d.pPrev = pPrefix->d.pNext = NULL;

            pPrefix->d.pPostfix->nBlockPoolIndex = -1;  // 负数代表这个内存块不在池中了,而是被使用了
            pPrefix->d.pPostfix->nActualSize = nSize;  // 记录申请的真实尺寸
            m_nBlocksAllocated[i] ++;  // 计数分配的该尺寸内存块
            return pPrefix->pMem;  // 返回真正可使用的指针地址(去掉前缀)
        }

        // 从页中分配
        const int nAllocSize = nMinPower2;
        do {
            for (MemPageHeader *pPage = m_pFirstPage; pPage != NULL; pPage = pPage->d.pNext)  // 遍历每个页。这个可能会很慢,但有办法,以后再说
            {
                if (pPage->d.pEnd >= pPage->d.pLast + sizeof(Prefix) + nAllocSize + sizeof(Postfix))  // 页里有足够空间
                {
                    Prefix *pPrefix = (Prefix*) pPage->d.pLast;
                    pPrefix->d.pPostfix = (Postfix*)(pPrefix->pMem + nAllocSize);
                    pPrefix->d.pPostfix->pPrefix = pPrefix;
                    pPrefix->d.pPostfix->nBlockPoolIndex = -1;  // 负数代表这个内存块不在池中了,而是被使用了
                    pPrefix->d.pPostfix->nActualSize = nSize;  // 记录申请的真实尺寸
                    pPrefix->d.pPrev = pPrefix->d.pNext = NULL;
                    pPrefix->d.pPage = pPage;
                    pPage->d.pLast += sizeof(Prefix) + nAllocSize + sizeof(Postfix);  // 移动页尾巴(页里空余内存的起始指针)
                    m_nBlocksAllocated[i] ++;  // 计数分配的该尺寸内存块
                    return pPrefix->pMem;  // 返回真正可使用的指针地址(去掉前缀)
                }
            }
        } while (AllocPage());  // 所有页都没有足够内存,现在我们需要分配一个新的页

        std::cerr << "Memory allocation failure" << std::endl;
        return NULL;
    }

    return AllocLarge(nSize, szFileName, nLine, bIsArray);  // 分配一块大内存
}

休息一下吧,下一章再说

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值