【C++】内存池的概念与简单示例

概念
我们每次使用new T来初始化类型T的时候, 其实都发生以下两步操作:

  1. 内存分配,这一步使用的其实是operator new(也可以认为就是C语言中的malloc),这一步是直接和操作系统打交道的,操作系统需要经过相对繁琐的过程才能将一块指向空闲内存的指针返回给用户,所以这也是new比较耗时的一部分。
malloc会用到brk和mmap这两个系统调用来获取内存。
brk通过增加program break location(brk)地址来获取内存。
mmap在memory mapping segment中创建一块内存返回给malloc。
  1. 构造函数初始化内存, 既然内存分配耗时,那我们很容易想到的就是一次性分配一大块内存,然后在用户需要的时候再划分其中一部分给用户,这样的话,一次分配, 多次使用, 在特定的需求场景下是可以较大的提高效率的(比如libuv中频繁的申请和释放内存),而用来管理这所谓的一大块内存的数据结构, 也就是今天我们要说的内存池
另外一个好处在于, 频繁地使用new将导致系统内存空间碎片化,
容易导致的后果就是很难找到一块连续的大块内存,
造成内存碎片(非连续),空间利用率低。

内存碎片分为内碎片和外碎片两部分。
内碎片是指分配给作业的存储空间中未被利用的部分;
外碎片是指系统中无法利用的小存储块。

外碎片问题
在这里插入图片描述
目前操作系统普遍采用的段页式内存分配方式就是:
将进程的内存区域分为不同的段,然后将每一段由多个固定大小的页组成。
通过页表机制,使段内的页可以不必连续处于同一内存区域,从而减少了外部碎片。
然而同一页内仍然可能存在少量的内部碎片,只是一页的内存空间本就较小,从而使可能存在的内部碎片也较少。
在这里插入图片描述

简单的代码设计思路

  1. 通过维护一个freeMemoryNodeList链表。当要申请空间的时候,就从链表上面摘下一个结点;要释放一个空间的时候,就从将释放的结点重新插入到链表里面。
  2. 每个结点的大小是提前通过template传入的,就是要分配的对象的大小。

代码示例

SimpleMemoryPool.hpp

#pragma once
namespace U
{
	template<int ObjectSize, int NumOfObjects = 20>
	class SimpleMemoryPool
	{
	private:
		struct MemoryNode
		{
			char data[ObjectSize];
			MemoryNode* pNext;
		};

		MemoryNode* freeMemoryNodeList;
		MemoryNode* pBack;//用于指向被释放动态申请的数组空间的初始位置,对于动态申请的数组空间,只能释放其初始位置

	private:
		MemoryNode* _InitMemoryNodeList()
		{
			MemoryNode* pNode = new MemoryNode[NumOfObjects];
			int count = 0;
			while (count < NumOfObjects - 1)
			{
				pNode[count].pNext = &pNode[count + 1];
				count++;
			}
			pNode[NumOfObjects - 1].pNext = nullptr;
			return pNode;
		}
	public:
		SimpleMemoryPool()
		{
			this->freeMemoryNodeList = _InitMemoryNodeList();
			pBack = this->freeMemoryNodeList;
		}

		~SimpleMemoryPool()
		{
			std::cout << "调用SimpleMemoryPool" << std::endl;

			if (this->pBack != nullptr)
				delete[] this->pBack;
		}


		void* Malloc()
		{
			if (nullptr == this->freeMemoryNodeList)
			{
				//重新分配一段内存空间
				std::cout << "重新添加内存空间" << std::endl;
				MemoryNode* pNode = _InitMemoryNodeList();
				//指向新的内容空间
				freeMemoryNodeList = pNode;
				pBack = pNode;
			}

			std::cout << "分配一个空间" << std::endl;
			MemoryNode* pRet = freeMemoryNodeList;
			freeMemoryNodeList = freeMemoryNodeList->pNext;
			pRet->pNext = nullptr;
			return pRet;
		}


		void Free(void* node)
		{
			std::cout << "释放一个空间" << std::endl;
			MemoryNode* pNode = (MemoryNode*)node;
			pNode->pNext = this->freeMemoryNodeList;//将需要释放的节点插入空闲节点头部
			this->freeMemoryNodeList = pNode;
		}

	};
}

main.cpp

class TestClass
{
private:
	static int count;
	int curIndex;

public:
	TestClass()
	{
		curIndex = count++;
	}

	~TestClass() {}

	void Print()
	{
		std::cout << this << ":";
		std::cout << "the " << curIndex << " object" << std::endl;
	}

	void* operator new(size_t size);
	void operator delete(void* p);

	void* operator new[](size_t size);
	void operator delete[](void* p, size_t size);
};

int TestClass::count = 0;

U::SimpleMemoryPool<4, 5> mp;

void* TestClass::operator new(size_t size)
{
	std::cout << "size:" << size << std::endl;
	return mp.Malloc();
}


void TestClass::operator delete(void* p)
{
	std::cout << "delete:" << p << std::endl;
	mp.Free(p);
}


void* TestClass::operator new[](size_t size)
{
	//size会比实际空间多4或8个字节,作用为记录数组长度以及内存对齐(至于具体是四个字节还是八个字节区别在于32位系统还是64位系统)
	std::cout << "size:" << size << std::endl;
	std::cout << "自定义的内存池不支持申请多个空间,所有默认调用系统malloc" << std::endl;
	return malloc(size);
}


void TestClass::operator delete[](void* p, size_t size)
{
	std::cout << "调用默认的free" << std::endl;
	std::cout << "delete [] size : " << size << std::endl;
	free(p);
}


int main(int argc, char* argv[])
{
	for (int i = 0; i < 3; i++)
	{
		TestClass* p = new TestClass;
		p->Print();
	}
	TestClass* p1 = nullptr;//3
	p1 = new TestClass;
	p1->Print();

	TestClass* p2 = new TestClass;
	p2->Print();

	//delete p1;

	TestClass* p3 = new TestClass;
	p3->Print();

	//delete p3;

	TestClass* p4 = new TestClass[5];
	delete[]p4;

	system("pause");
	return 0;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值