內存池

1、什麼是內存池

內存池是對內存分配的管理。正常創建變量的方式是new和malloc,但頻繁的new與delete,malloc與free同操作系統交互,會降低程序的運行效率,增加內存中的內存碎片數(剩餘的內存很足,但不是連續的內存,不足以被分配)。而內存池提前申請一片一定數量的、大小相等的內存備用。當需要時,在內存池中取用,不用時還給內存池。使得內存分配效率得到提升。

2、適用場景

頻繁的new與delete,malloc與free固定大小的內存對象。

3、組成分爲三部分,分別是:內存單元,內存塊,內存池。

(1)內存單元:實際上使用的內存。

(2)內存塊:由內存塊對象和多個大小相等的內存單元組成(內存單元不在內存塊的作用域下,但內存上是與內存塊相連的)。

typedef unsigned short USHORT;

//內存塊
class MemoryBlock
{
public:	//函數
	//內存單元的大小和數目
	MemoryBlock(USHORT m_usUnitSize, USHORT nUnitNumber);
	~MemoryBlock() {};
	static void*    operator new    (size_t, USHORT m_usUnitSize, USHORT nUnitNumber);
	static void     operator delete (void* pBlock);

public: //變量
	USHORT          m_usUnitAllSize;	//內存單元的總大小
	USHORT          m_usFreeUnitNum;	//該內存塊還有多少可分配的單元(自由單元的數目)
	USHORT          m_usFirstFreeUnitID;//第一個自由單元的編號 編號從1開始
	MemoryBlock*    pNext;				//指向下一個內存塊
	char            aData[1];			//用於標記分配單元開始的位置,分配單元從aData的位置開始
};

(3)內存池:由內存塊鏈表組成。

//內存池
class MemoryPool
{
public:
	MemoryPool(USHORT usUnitSize,
		USHORT usGrowUnitNum = 64,
		USHORT usUnitNumber = 8);
	~MemoryPool();
	void*           Alloc();
	void            Free(void* pFree);

private:
	USHORT         m_usUnitNumber;     //第一次創建內存塊的內存單元數目
	USHORT         m_usGrowUnitNum;    //再次創建內存塊的內存單元數目
	USHORT         m_usUnitSize;       //內存單元的大小
	MemoryBlock*   pBlock;             //內存塊鏈表的表頭
};

4、測試代碼

(1)MemoryPool.h

#include <stdlib.h>

#define  MEMPOOL_ALIGNMENT 8            //對齊長度,應用內存池變量類型的長度
typedef unsigned long ULONG;
typedef unsigned short USHORT;

//內存塊
class MemoryBlock
{
public:	//函數
	//內存單元的大小和數目
	MemoryBlock(USHORT m_usUnitSize, USHORT nUnitNumber);
	~MemoryBlock() {};
	static void*    operator new    (size_t, USHORT m_usUnitSize, USHORT nUnitNumber);
	static void     operator delete (void* pBlock);

public: //變量
	USHORT          m_usUnitAllSize;	//內存單元的總大小
	USHORT          m_usFreeUnitNum;	//該內存塊還有多少可分配的單元(自由單元的數目)
	USHORT          m_usFirstFreeUnitID;//第一個自由單元的編號 編號從1開始
	MemoryBlock*    pNext;				//指向下一個內存塊
	char            aData[1];			//用於標記分配單元開始的位置,分配單元從aData的位置開始
};

//內存池
class MemoryPool
{
public:
	MemoryPool(USHORT usUnitSize,
		USHORT usGrowUnitNum = 64,
		USHORT usUnitNumber = 8);
	~MemoryPool();
	void*           Alloc();
	void            Free(void* pFree);

private:
	USHORT         m_usUnitNumber;     //第一次創建內存塊的內存單元數目
	USHORT         m_usGrowUnitNum;    //再次創建內存塊的內存單元數目
	USHORT         m_usUnitSize;       //內存單元的大小
	MemoryBlock*   pBlock;             //內存塊鏈表的表頭
};

(2)MemoryPool.cpp

#include "MemoryPool.h"

MemoryBlock::MemoryBlock(USHORT m_usUnitSize, USHORT nUnitNumber)
	: m_usUnitAllSize(nUnitNumber * m_usUnitSize),
	m_usFreeUnitNum(nUnitNumber - 1),  //構造的時候,就已將第一個單元分配出去了,所以減一
	m_usFirstFreeUnitID(1),               //從1開始
	pNext(NULL)
{
	//初始化數組鏈表,將每個分配單元的下一個分配單元的序號寫在當前單元的前兩個字節中
	char* pData = aData;
	//最後一個位置不用寫入
	for (USHORT i = 1; i < nUnitNumber ; ++i)
	{
		//給內存單元附上編號,從1開始,最後一個內存單元無編號
		*reinterpret_cast<USHORT*>(pData) = i;
		//(*(USHORT*)pData) = i;
		pData += m_usUnitSize;
	}
}

void* MemoryBlock::operator new(size_t, USHORT m_usUnitSize, USHORT nUnitNumber)
{
	return ::operator new(sizeof(MemoryBlock) + m_usUnitSize * nUnitNumber); //標準作用域下的new
}

void MemoryBlock::operator delete(void* pBlock)
{
	::operator delete(pBlock);
}

MemoryPool::MemoryPool(USHORT usUnitSize, USHORT usGrowUnitNum, USHORT usUnitNumber )
{
	m_usUnitNumber = usUnitNumber;
	m_usGrowUnitNum = usGrowUnitNum;
	pBlock = NULL;
	if (usUnitSize > 4)
		m_usUnitSize = (usUnitSize + (MEMPOOL_ALIGNMENT - 1)) & ~(MEMPOOL_ALIGNMENT - 1);
	else if (usUnitSize < 2)
		m_usUnitSize = 2; //每個單元的前兩個字節存下一個自由單元的編號,所以每個單元最小爲2
	else
		m_usUnitSize = 4;
}

MemoryPool::~MemoryPool()
{
	MemoryBlock* pMyBlock = pBlock;
	while (pMyBlock != NULL)
	{
		pMyBlock = pMyBlock->pNext;
		delete(pMyBlock);
	}
}

void* MemoryPool::Alloc()
{
	//當內存塊鏈表爲空時,申請內存塊
	if (NULL == pBlock)
	{
		//首次生成MemoryBlock,new帶參數,new了一個MemoryBlock類
		pBlock = (MemoryBlock*)new(m_usUnitSize, m_usUnitNumber) MemoryBlock(m_usUnitSize, m_usUnitNumber);
		return (void*)pBlock->aData;
	}

	//找到符合條件的內存塊
	MemoryBlock* pMyBlock = pBlock;
	while (pMyBlock != NULL && 0 == pMyBlock->m_usFreeUnitNum)
		pMyBlock = pMyBlock->pNext;

	//當內存塊鏈表不爲空時,尋找自由內存單元
	if (pMyBlock != NULL)
	{
		//內存單元的首地址 + 地址偏移量 = 自由單元首地址
		char* pFree = pMyBlock->aData + pMyBlock->m_usFirstFreeUnitID * m_usUnitSize;
		//第一個自由單元編號更改
		pMyBlock->m_usFirstFreeUnitID = *((USHORT*)pFree);
		//自由單元數目減一
		pMyBlock->m_usFreeUnitNum--;
		//返回空閒內存
		return (void*)pFree;
	}
	else
	{
		//沒有找到還有自由分配單元的內存塊,需要重新向進程堆申請一個內存塊。
		if (0 == m_usGrowUnitNum)
			return NULL;
		pMyBlock = (MemoryBlock*)new(m_usUnitSize, m_usGrowUnitNum) MemoryBlock(m_usUnitSize, m_usGrowUnitNum);

		if (NULL == pMyBlock)
			return NULL;

		//將新申請的內存塊插入到內存塊鏈表頭位置
		pMyBlock->pNext = pBlock;
		pBlock = pMyBlock;

		return (void*)pMyBlock->aData;
	}
}

void MemoryPool::Free(void* pFree)
{
	//找到p所在的內存塊
	MemoryBlock* pMyBlock = pBlock;
	MemoryBlock* PreBlock = pMyBlock;	//pMyBlock前一個內存塊
	//pMyBlock 不在內存範圍內進入
	while (pMyBlock != NULL && (pMyBlock->aData > pFree || pFree > pMyBlock->aData + pMyBlock->m_usUnitAllSize))
	{
		PreBlock = pMyBlock;
		pMyBlock = pMyBlock->pNext;
	}
	if (NULL != pMyBlock)      //該內存在本內存池中pMyBlock所指向的內存塊中
	{
		//將原指向的自由單元ID賦值給新的自由單元前兩個字節
		*((USHORT*)pFree) = pMyBlock->m_usFirstFreeUnitID;
		//將新的自由單元ID賦值給內存塊中指向自由單元的變量
		pMyBlock->m_usFirstFreeUnitID = (USHORT)((ULONG)pFree - (ULONG)pMyBlock->aData) / m_usUnitSize ;
		pMyBlock->m_usFreeUnitNum++;

		//判斷是否需要釋放內存
		if (pMyBlock->m_usUnitAllSize == pMyBlock->m_usFreeUnitNum * m_usUnitSize)
		{
			//在鏈表中刪除該block
			//如果內存塊爲頭結點,更新頭結點,否則替換節點
			if (pBlock == pMyBlock)
			{
				pBlock = pMyBlock->pNext;			
			}
			else
			{
				PreBlock->pNext = pMyBlock->pNext;
			}
			delete(pMyBlock);
		}
		else
		{
			if (NULL == pBlock->pNext) return;
			//將該block插入到隊首
			PreBlock = pBlock;
			pBlock = pMyBlock;
			pBlock->pNext = PreBlock;
		}
	}
}

(3)測試代碼

#include <iostream>
#include "MemoryPool.h"

class CTest
{
public:
	CTest():m_idata1(1),m_idata2(2) { };
	~CTest() {};
	void*       operator new (size_t);
	void        operator delete(void* pTest);
public:

	static      MemoryPool Pool;
	int         m_idata1;
	int         m_idata2;
};

void CTest::operator delete(void* pTest)
{
	Pool.Free(pTest);
}


void* CTest::operator new(size_t)
{
	return (CTest*)Pool.Alloc();
}

MemoryPool CTest::Pool(sizeof(CTest));

int main()
{
	{
		CTest* p1 = new CTest;
		std::cout << p1 << std::endl;
		CTest* p2 = new CTest;
		std::cout << p2 << std::endl;
		delete p1;
		delete p2;
		p1 = NULL;
		p2 = NULL;
	}
	system("pause");
}

5、對內存池的簡單理解與代碼的實現和修改參考於以下文章:

(1)http://www.ibm.com/developerworks/cn/linux/l-cn-ppp/index6.html

發佈了42 篇原創文章 · 獲贊 52 · 訪問量 6594
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章