简易内存池实现

修订

2013-04-15

  1. 代码中的New函数,使用DWORD保存4字节对齐后的大小,避免wSize接近65536时对齐为65536后溢出。
  2. Block结构添加prev指针,used及unused修改为双链表,避免Delete时的定位开销。

介绍

项目需要自定义一个表格控件,涉及到多行多列文本的显示。
考虑到字符串指针较多的情况下,性能较低以及容易产生内存碎片,为此实现了一个内存池。
该内存池具有以下特点:

  1. 不能分配接近及超过64k的内存(一般情况下,表格列不会包含如此长的文本)。
  2. 分配次数较多,单个释放次数较少(表格内容填充后不易变更,可以在删除表格时一次释放整个内存池)。
  3. 允许一定内存冗余,以减少内存碎片(单个表格需要的内存总量不会太大,可以接受较小比例的冗余)。

数据结构

首先,以64K为单位向系统申请内存作为内存区,供使用者按实际需要细分为内存块。
每块内存区是一个Area结构,每块内存块是一个Block结构。
当64k不够用的时候,重新申请一个Area,与当前所有已申请的Area组成一个链表。

Area

┌────┬─────┬───┬────┬────────────┐
│used│unsed│end│next│buffer      │
│    │     │   │    ├───────┬────┤
│    │     │   │    │alloced│free│
└────┴─────┴───┴────┴───────┴────┘

used 指向Area内已分配且已使用的Block链表
ununed 指向Area内已分配且未使用的Block链表
end 指向已分配内存结束位置,新申请时从该位置分配内存
next 指向下一个Area,组成Area链表,以便在当前Area分配不足时申请新的Area
buffer 可分配内存,尺寸为64k减去前面几个字段的大小

Block

┌────┬────┬────┬────┬──────┐
│size│area│prev│next│memory│
└────┴────┴────┴────┴──────┘

size 表示实际可使用内存大小,以便Block重复使用时进行大小匹配
area 指向本Block所属Area
prev 指向上一个Block,组成used或unsed双链表
next 指向下一个Block,组成used或unsed双链表

算法

分配内存时,
首先查询所有Area的unused链表,如果找到合适的Block,可以直接使用。
如果没有合适的,在现有Area的待分配内存中分配新的Block。
如果现有Area的待分配内存都不满足需求大小,则重新申请一个Area并进行分配。

循环遍历Area链表
{
	遍历unused链表
	{
		根据需要内存大小寻找最合适的已分配且未使用Block
	}
}
如果找到合适的Block
{
	从所属Area的unsed链表移除
	添加到所属Area的used链表
	返回该Block的实际可使用内存
}
循环遍历Area链表
{
	如果当前Area的待分配内存>=Block头+实际需要内存大小
	{
		按照Block头+实际需要内存大小分配Block
		设置Block的size和area
		移动Area的End位置
		返回Block的实际可使用内存
	}
}
新申请Area
加入到Area链表
在新Area上申请Block

释放内存时,
首先获取指针所在的Block结构,然后通过Block获取所属Area
然后从Area的used链表移除Block。
最后将Block放入Area的unsed链表。

从内存指针减去Block头获取Block结构地址
遍历Block所属Area的used链表
{
	如果找到
	{
		从used链表移除
	}
}
添加到所属Area的ununed链表

代码

为了节省内存空间,以及避免32位/64位系统不同指针长度带来的不确定性,Area及Block内的各种指针尽量使用WORD表示0-65535的偏移值来代替。

// MemPool.h
#pragma once


//------------------------------------------------------------------------------
// 不能分配大于 AREA_SIZE-sizeof(Block) 的内存
class MemPool
{
    struct Block;
    struct Area
    {
        WORD    pUsed;        // 已使用的首个内存块距离Area首地址的偏移
        WORD    pUnused;    // 未使用的首个内存块距离Area首地址的偏移
        WORD    wEnd;        // 当前已分配内存距离首地址pBuffer的偏移
        WORD    wReserved;    // 保留以便对齐
        Area    *pNext;

#define HEAD_SIZE    (sizeof(WORD) + sizeof(WORD) + sizeof(WORD) + sizeof(WORD) + sizeof(Area*))
#define AREA_SIZE    (65536 - HEAD_SIZE)

        char    pBuffer[AREA_SIZE];    // 预分配内存
    };

    struct Block
    {
        WORD    wSize;        // 分配尺寸
        WORD    pArea;        // Block距离所属Area首地址的偏移
        WORD    pPrev;
        WORD    pNext;
    };

public:
    MemPool(void);
    ~MemPool(void);

    void Cleanup(void);

    void *New(WORD wSize);
    void Delete(void *pPointer);

private:
    Area    m_xArea;

    void *New(Area *pArea, WORD wSize);
};


// MemPool.cpp
#include <assert.h>
#include <Windows.h>
#include "MemPool.h"


//------------------------------------------------------------------------------
MemPool::MemPool(void)
{
    memset(&m_xArea, 0, sizeof(m_xArea));
}

MemPool::~MemPool(void)
{
    Cleanup();
}

void MemPool::Cleanup(void)
{
    for (Area *pNext=m_xArea.pNext; NULL!=pNext; )
    {
        Area *pDelete = pNext;
        pNext = pNext->pNext;
        delete pDelete;
    }

    memset(&m_xArea, 0, sizeof(m_xArea));
}

void *MemPool::New(WORD wSize)
{
    DWORD wNeedSize = ((wSize+3) & ~3);    // 四字节对齐
    assert(wNeedSize <= AREA_SIZE - sizeof(Block));

    // 查找最合适的未使用内存块
    long lBestOffset = 65536;
    Block *pBestBlock = NULL;

    for (Area *pArea=&m_xArea; NULL!=pArea; pArea=pArea->pNext)
    {
        for (WORD pUnused=pArea->pUnused; 0!=pUnused; )
        {
            Block *pBlock = (Block*)((char*)pArea + pUnused);

            long lOffset = pBlock->wSize - wNeedSize;
            if (lOffset>=0 && lOffset<lBestOffset)
            {
                lBestOffset = lOffset;
                pBestBlock = pBlock;
            }

            pUnused = pBlock->pNext;
        }
    }

    if (NULL!=pBestBlock && lBestOffset<32)    // 有未使用内存块且冗余可以接受
    {
        Area *pArea = (Area *)((char*)pBestBlock - pBestBlock->pArea);

        // 从未使用列表中移除
        if (0 != pBestBlock->pPrev)
        {
            Block *pPrevBlock = (Block*)((char*)pArea + pBestBlock->pPrev);
            pPrevBlock->pNext = pBestBlock->pNext;
        }
        else pArea->pUnused = pBestBlock->pNext;
        if (0 != pBestBlock->pNext)
        {
            Block *pNextBlock = (Block*)((char*)pArea + pBestBlock->pNext);
            pNextBlock->pPrev = pBestBlock->pPrev;
        }

        // 添加到使用列表
        if (0 != pArea->pUsed)
        {
            Block *pPrevBlock = (Block*)((char*)pArea + pArea->pUsed);
            pPrevBlock->pPrev = pBestBlock->pArea;
        }
        pBestBlock->pNext = pArea->pUsed;
        pArea->pUsed = pBestBlock->pArea;

        return (char*)pBestBlock + sizeof(Block);
    }

    // 重新分配
    Area *pLastArea = NULL;

    // 在已有内存上分配新内存块
    for (Area *pArea=&m_xArea; NULL!=pArea; pArea=pArea->pNext)
    {
        if ((long)wNeedSize <= (long)(AREA_SIZE - sizeof(Block) - pArea->wEnd))
            return New(pArea, (WORD)wNeedSize);

        pLastArea = pArea;
    }

    // 重新申请内存
    if (Area *pArea = new Area)
    {
        memset(pArea, 0, sizeof(Area));
        pLastArea->pNext = pArea;

        return New(pArea, (WORD)wNeedSize);
    }

    return NULL;
}

void MemPool::Delete(void *pPointer)
{
    Block *pBlock = (Block *)((char *)pPointer - sizeof(Block));
    Area *pArea = (Area *)((char*)pBlock - pBlock->pArea);

    // 从使用列表中移除
    if (0 != pBlock->pPrev)
    {
        Block *pPrevBlock = (Block*)((char*)pArea + pBlock->pPrev);
        pPrevBlock->pNext = pBlock->pNext;
    }
    else pArea->pUsed = pBlock->pNext;
    if (0 != pBlock->pNext)
    {
        Block *pNextBlock = (Block*)((char*)pArea + pBlock->pNext);
        pNextBlock->pPrev = pBlock->pPrev;
    }

    // 添加到未使用列表
    if (0 != pArea->pUnused)
    {
        Block *pPrevBlock = (Block*)((char*)pArea + pArea->pUnused);
        pPrevBlock->pPrev = pBlock->pArea;
    }
    pBlock->pNext = pArea->pUnused;
    pArea->pUnused = pBlock->pArea;
}

void *MemPool::New(Area *pArea, WORD wSize)
{
    // 分配新内存块
    Block *pBlock = (Block*)((char*)pArea + HEAD_SIZE + pArea->wEnd);
    pBlock->pArea = HEAD_SIZE + pArea->wEnd;
    pBlock->wSize = wSize;

    // 添加到使用列表
    if (0 != pArea->pUsed)
    {
        Block *pPrevBlock = (Block*)((char*)pArea + pArea->pUsed);
        pPrevBlock->pPrev = pBlock->pArea;
    }
    pBlock->pNext = pArea->pUsed;
    pArea->pUsed = (char*)pBlock - (char*)pArea;

    pArea->wEnd += sizeof(Block) + wSize;
    return (char*)pBlock + sizeof(Block);
}

其他

分配时查找unused链表需要耗费一定时间,后续可以考虑采用辅助算法加速查找。

原文地址:https://www.cnblogs.com/armageddon/p/3020110.html