C++内存管理 --- 内存池

本文深入探讨了内存池技术,解析了其如何通过一次性申请大块内存并按需分配小块,来提升程序运行效率和减少内存浪费。文章详细介绍了三种内存池实现方式:单独类内存池、改进版单独类内存池及静态分配器,展示了如何优化内存管理和提高运行速度。

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

引子
  • new实际分配的内存:
    在这里插入图片描述
  • 从new实际分配的内存可以看出,除了我们实际需要的内存之外,系统分配了很多增加的内容,例如开头和结尾的两个cookie,各8byte。这两个cookie记录了实际分配空间的大小,同时在delete的时候告诉系统实际需要释放的空间。
  • 为了减少大量小空间被分配时,过多cookie造成的内存浪费,我们引入了内存池。内存池的思想是一次性申请一大块空间,每次使用时返回一小块,这样做的优点在于:既减少了申请内存操作的次数,提升了程序运行的效率;又减少了cookie带来的内存浪费。
  • 通常内存池内部是一个单向链表将内存片串联起来,便于管理;
    在这里插入图片描述
一、单独类内存池
class A
{
public:
	A(int x) :i(x) {};
	int get() { return i; }

	void* operator new(size_t);
	void operator delete(void*, size_t);

private:
	A* next;
	static A* freeStore;     //指向可用首地址
	static const int Achunk; //内存池可容纳的对象个数

private:
	int i;
};

A* A::freeStore = 0;
const int A::Achunk = 24;

void* A::operator new(size_t size)
{
	A* p;
	if (!freeStore)
	{
	//linked list is empty
		size_t chunk = Achunk * size;
		freeStore = p = reinterpret_cast<A*>(new char[chunk]);
		//将一大快分片
		for (; p != &freeStore[Achunk - 1]; ++p)
		{
			p->next = p + 1;
		}
		p->next = 0;
	}
	p = freeStore;
	freeStore = freeStore->next;
	return p;
}

void A::operator delete(void* p, size_t)
{
	// 将delete object插回free list前端
	(static_cast<A*>(p))->next = freeStore;
	freeStore = static_cast<A*>(p);
}
  • 内存池的优势在于提高运行速度,减少内存浪费;
  • 这样处理存在的问题时是:是否有必要为了消除cookie而额外引入一个4byte的指针,对于上例来说,膨胀率等于100%,以下例子巧妙解决了这个问题;
二、改进版单独类内存池
class A
{
private:
	struct ARep
	{
		int i;
		char type;
	};
private:
	union 
	{
		ARep rep;
		A* next;
	};

public:
	A(int x) :i(x) {};
	int get() { return i; }

	void* operator new(size_t);
	void operator delete(void*, size_t);

private:
	static A* freeStore;      //指向可用内存首地址
	static const int Achunk;  //内存池可容纳的对象个数

private:
	int i;
};

A* A::freeStore = 0;
const int A::Achunk = 24;

void* A::operator new(size_t size)
{
	if (size != sizeof(A))
		return ::operator new(size);
	A* p = freeStore;
	if (p)
		freeStore = p->next;
	else
	{
		//linked list is empty
		size_t chunk = Achunk * size;
		A* newBlock = static_cast<A*>(::operator new(Achunk * sizeof(A)));
		//将一大快分片,并串联
		for (int i = 1; i < Achunk; ++i)
		{
			newBlock[i].next = &newBlock[i + 1];
		}
		newBlock[Achunk - 1].next = 0; //结束list
		p = newBlock;
		freeStore = &newBlock[1];
	}
	return p;
}

void A::operator delete(void* p, size_t)
{
	// 将delete object插回free list前端
	(static_cast<A*>(p))->next = freeStore;
	freeStore = static_cast<A*>(p);
}
  • 使用嵌入式指针后,我们借用A对象所占空间的前4字节,用于连接空闲内存块(储存指针),一旦这一块被分配出去,这4个字节不再需要;可以说嵌入式指针和成员变量是在不同时期使用了同一块内存
  • 这两种内存池的实现方式类似,都是为单独的类进行内存管理,这失去了重用性,我们如何解决这一问题呢?
三、静态分配器
class m_allocator
{
private:
	struct obj
	{
		struct obj* next; //embedded pointer
	};
public:
	void* allocate(size_t);
	void deallocate(void*, size_t);
private:
	obj* freeStore = nullptr;
	const int CHUNK = 5;
};

void* m_allocator::allocate(size_t size)
{
	obj* p;
	if (!freeStore)
	{
		size_t chunk = CHUNK * size;
		freeStore = p = (obj*)malloc(chunk);
		for (int i = 0; i < CHUNK - 1; ++i)
		{
			p->next = (obj*)((char*)p + size);
			p = p->next;
		}
		p->next = nullptr;
	}
	p = freeStore;
	freeStore = freeStore->next;
	return p;

	
}

void m_allocator::deallocate(void* p, size_t)
{
	((obj*)p)->next = freeStore;
	freeStore = (obj*)p;
}

在这里插入图片描述

  • 这样一来,app classes 不再与内存分配的细节纠葛,而全权交给allocator去管理;
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值