C++ 内存管理(一)----per-class allocator

一、per-class allocator 1

想利用类内重载operator new去接管内存的分配,然后利用内存池的观念【即创建出一大段连续空间的内存,然后将其切割成一小段一小段】,将创建的元素对象放在内存池切分好的各分段小内存片中,这样避免了多次调用new而造成生成多个带有cookie的内存空间。通过内存池的观念,可以生成一大段只带有两个头尾cookie的内存空间,而该一大段内存空间又被切分成每一小段的内存空间,且其中的每一小段内存空间片都可以共享这一整体的cookie信息

#include<cstddef>
#include<iostream>
using namespace std;

class Screen{
public:
	Screen(int x): i(x){};
	int get(){
		return i;
	}
	void* operator new(size_t);  // 其实这里最规范的写法应该添加staic关键字!!
	void operator delete(void*, size_t); // 虽然不添加系统也会默认将其看成 static函数去调用!!

private:
	Screen* next;  // 创建一个自身类型的指针对象
	static Screen* freeStore; // 静态变量必须类内声明,类外定义赋初值
	static const int screenChunk;
private:
	int i;
};
Screen* Screen::freeStore = 0;  // 静态变量必须类内声明,类外定义赋初值
const int Screen::screenChunk = 24;


void* Screen::operator new(size_t size){
	Screen* p;
	if(!freeStore){
		// linked list是空的,则申请一大块内存空间
		size_t chunk = screenChunk * size;
		freeStore = p = reinterpret_cast<Screen*>(new char[chunk]);
		// 将new出来的一大块内存块分割片片,当作linked list串接起来
		for(; p != &freeStore[screenChunk - 1]; ++p)
			p->next = p + 1;
		p->next = 0; 
	}
	p = freeStore;
	freeStore = freeStore->next;
	return p;
}
void Screen::operator delete(void* p, size_t){
	// 将所需删掉的object在整个linked list中的位置调整为后面空白表free list【即未使用链表】的最前端
	(static_cast<Screen*>(p))->next = freesStore;
	freeStore = static_cast<Screen*>(p);
}

因为为了能将一大段内存空间切分成一小段一小段,然后通过单向链表的形式串接起来,所以必须多引入一个Screen* next指针。但这又会增加class Screen的大小【增加了4字节】。

另外,static Screen* freeStore 和 static const int screenChunk是静态成员变量,是声明class Screen就创建出来的了【而且被其所有类对象共享的】,并不是在创建每一个Screen类对象时才被创建出来,因而并不计算入类对象的大小中【所以生成的每一个类对象其大小为4+4=8】。

在这里插入图片描述左边是重载了operator new的结果,所以其每个指针地址大小相差为8【类对象的大小】;
而右边是没有重载operator new而调用的是::operator new,所以生成的每个类对象都是带有cookie的,因此每个指针地址大小相差为16【加上头尾两个cookie的大小 4 * 2 = 8

首先创建出100个存放Screen类型指针的数组,然后进入for循环并进入内部的new Screen():

1. void* men = operator new(sizeof(Screen));
调用class Screen重载的operator new()函数,进入其源码传入参数为size=sizeof(Screen)=4。进入函数内部,先创建一个Screen类型的指针p,然后由于是第一次进入所以 freeStore=0,则在if条件判断时,! freeStore 为真进入if内部执行【即此时linked list为空,需要申请一大块】。
然后所需申请的一大块的内存大小chunk指定每个切分段的大小(screenChunk) * 所需切分存放个数(size)
chunk = screenChunk * size。然后调用new char[chunk]创建出一大块内存,并将指向该大块内存空间的指针转型为Scree*类型指针赋值给 freeStore 和 p 。此时freeStore 和 p 均是指向新分配的一大块内存空间的头部!
接下来,便是将一大块分割片片的操作
首先 freeStore[ ] 便是将刚才分配的空间通过数组下标切分成一段段表示了【24份】,但此时虽然切分好了,却仍没实现用指针串接起来每一小段。而p为指向一大段内存空间的首地址& freeStore[screenChunk - 1] 为一大段内存空间中的指向最后一小块的地址,p != freeStore[screenChunk]满足条件则进入for循环。进入内部操作,因为是在连续内存空间中,所以 p->next = p + 1中,p + 1表示将p指针移到临近的一个地址位置【因为类对象大小为8,所以指针+1相当于地址+8】并赋值给next指针【p->next表示取出Screen类对象中的next指针变量】。也即类对象中的next指针变量记录了连续内存空间中临近的那块内存空间的地址,这样就实现了将p所指向的内存空间的下一块临近内存空间串接起来了。
++p则此时p变成指向刚才临近的内存空间,然后p仍是不等于大段内存空间中的指向最后一小块的地址,所以继续进入for循环内部操作,将其下一个临近的内存空间串接起来。以此类推,一直到p变成指向最后一小块内存空间。退出for循环,此时已将24小段的内存空间串接起来了,并且这时操作 p->next=0,表示将最后一小段内存空间的下一个地址赋值为0即空。退出 if 条件执行操作。
p = freeStore重新让p指向该大段内存空间的头端,然后 freeStore = freeStore->next 即freeStore下移一位变成指向临近的内存空间。然后返回p。
2. p[0] = static_cast<Complex*>(men); 将指针转为Screen*类型并赋值给p[0]
3. p[0]->Screen::Screen(0) 调用传参构造函数 **Screen(int x): i(x){}**创建出一个Screen类对象并放置于p[0]所指向的一小段的内存空间中。

然后,进行下一次的for循环,p[1] = new Screen(1):

1. void* men = operator new(sizeof(Screen));
此时调用operator new进入内部操作 if(!freeStore) 的条件判断时,因为freeStore此时已不为0,而是指向p[0]->next所指向的内存空间了。所以此时不进入if内部操作,而是直接:
p = freeStore,p变为指向第二小块内存空间
freeStore = freeStore->next, freeStore变为指向第三小块内存空间
return p,返回p
2. p[1] = static_cast<Complex*>(men); 将指针转为Screen*类型并赋值给p[1]
3. p[1]->Screen::Screen(1) 调用传参构造函数 **Screen(int x): i(x){}**创建出一个Screen类对象并放置于p[1]所指向的一小段的内存空间中。

一直进行到 i=23 时,此时:

1. void* men = operator new(sizeof(Screen));
p = freeStore,此时p已经指向最后一小分块了
freeStore = freeStore->next, freeStore变为指向最后一小段内存空间下一个地址即为0了
return p,返回p
2. p[23] = static_cast<Complex*>(men); 将指针转为Screen*类型并赋值给p[23]
3. p[23]->Screen::Screen(23) 调用传参构造函数 **Screen(int x): i(x){}**创建出一个Screen类对象并放置于p[23]所指向的一小段的内存空间中。
此时这一大段只带两个cookie却存放了24小段Screen类对象的长段内存空间便利用完了。

此时,进行到i=24 时(又重新开始生成一大段的内存空间并进行切分):

1. void* men = operator new(sizeof(Screen));
进入operator new()函数时,此时的freeStore=0,所以又进行一次创建新的一大段内存空间的操作,且切分成24小段并串接起来。。。
2. p[24] = static_cast<Complex*>(men); 将指针转为Screen*类型并赋值给p[24]
3. p[24]->Screen::Screen(24) 调用传参构造函数 **Screen(int x): i(x){}**创建出一个Screen类对象并放置于p[24]所指向的一小段的内存空间中。

以此类推,一直到放完Screen(99)创建出来的Screen类对象,才结束整个for循环【实际上是生成了5大段的长段内存空间,每一段切分成24片片内存空间且每一小段共享其所处长段的cookie】。

二、per-class allocator 2

这个版本通过union关键字来减少使用next而所占耗的内存!

class Airplane{
private:
	struct AirplaneRep{
		unsigned long miles;  
		char type; 
	}private:
	union{ // 此时union表示 rep 和 next 都是代表的同一个对象即其地址为同一个,只是定义了从不同角度去看这个对象而已
		AirplaneRep rep;
		Airplane* next;
	};
public:
	unsigned long getMiles(){
		return rep.miles;
	}
	char getType(){
		return rep.type;
	}
	void set(unsigned long m, char t){
		rep.miles = m;
		rep.type = t;
	}
public:
	static void* operator new(size_t size);
	static void* operator delete(void* deadObject, size_t size);
private:
	static const int BLOCK_SIZE;
	static Airplane* headOfFreeList; // 空表格的头部,初始值为0即指向空
};
Airplane* Airplane:headOfFreeList;
const int Airplane::BNLOCK_SIZE = 512;

void* Airplane::operator new(size_t size){
	if(size != sizeof(Airplane)) // 如果由于继承关系传入的size=sizeof(Airplane')大小不等于基础班的sizeof(Airplane)时,则调用::operator new!!
		return ::operator new(size);
		
	Airplane* p = headOfFreeList; // 将上一轮的空表的头端位置赋值给p
	
	if(p) // 如果p有效,则list(空表格)头部下移一个元素
		headOfFreeList = p->next;
		
	else{ // free list已空,申请一大块!!
		Airplane* newBlock = static_cast<Airplane*>
								(::operator new(BLOCK_SIZE * sizeof(Airplane)));
		// 将512小块串成一个free list,
		// 但跳过#0,因它将被传回作为本次成果,实际上#0即是指向一大块内存的头端地址【即newBlock】
		for(int i = 1; i < BLOCK_SIZE - 1; ++i)
			newBlock[i].next = &newBlock[i+1];
		newBlock[BLOCK_SIZE - 1].next = 0; // 最后的一小块的下一位指向空即结束list
		p = newBlock; // 实际上其为newBlock[0]的地址
		headOfFreeList = &newBlock[1]; // 在 新创建 完free list后,p指向头端且将要被使用,所以代表指向空白链表的头端的headOfFreeList此时必须下移一位即指向newBlock[1]的位置!!
	}
	return p;
}

void Airplane::operator delete(void* deadObject, size_t size){
	if(deadObject == 0)
		return;
	if(size != sizeof(Airplane)){
		::operator delete(deadObject);
		return;
	}
	Airplane* carcass = static_cast<Airplane*>(deadObject);
	carcass->next = headOfFreeList;
	headOfFreeList = carcass;
}

在这里插入图片描述
注意:上面两个版本的operator delete操作都没有将内存空间回收还回给系统,而是仍然存在的。虽然operator delete操作没有将这些分配的内存空间释放掉,但其仍在控制中即仍可继续重新使用只不过freeStore或headOfFreeList此时又重新跑回整个一大块内存空间的头端】,所以不算内存泄漏!

三、static allocator(per-class allocator 3)

上面的第二个版本虽然相比第一版本有改进了,但每写一个class都必须在其面重写一边member operator new和 member operator delete。所以有想法:

将分配特定尺寸区块的memory allocator包装成一个class allocator
这样每个allocator object都是个分配器体内维护一个free lists
不同类(如下面的class Foo,class Goo等) 里面调用生成的各自allocator objects维护不同的free lists。
在这里插入图片描述由上知,class Foo或class Goo其operator new或operator delete最终调用的都是allocator object所维护的free list而进行操作的!

另外,class Foo或class Goo中,应注意到:
static allocator myAlloc,即myAlloc必须是个静态成员变量,且在类外定义(赋初值)。如果其不是静态成员变量时,则在构建class Foo类对象时,是没办法调用到myAlloc的。因为非静态成员变量只能通过对象调用【但此时Foo对象还没生成又如何调用!!】。而myAlloc又是用来生成Foo类对象的,所以得通过类名调用即应设置为static类型。

而class allocator的源码如下:

class allocator{
private:
	struct obj{
		struct obj* next;
	};
public:
	static void* allocate(size_t);
	static void deallocate(void*, size_t);
private:
	obj* freeStore = nullptr;
	const int CHUNK = 5; // 标准库一般设置为20
};
void* allocator::allocate(size_t size){
	obj* p;
	if(!freeStore){
		// linked list为空,则申请一大块
		size_t chunk = CHUNK * size;
		freeStore = p =(obj*)malloc(chunk); // 这里直接调用malloc进行分配空间
		
		// 将分配的一大块切分成5小段,并串接起来
		for(int i = 0; i < (CHUNK - 1); ++i){
			p->next = (obj*)((char*)p + size);
			p = p->next;
			// 上面这两步相当于p->next = p + 1,
			// 只不过这里需要适应不同的类下的操作,因而设置成这种形式!!
		}
		p->next = nullptr; // 最后一小段的下一个位置指向空
	}
	p = freeStore;
	freeStore = freeStore->next;
	return p;
}
void allocator::deallocate(void* p, size_t){
	// 将要删除的*p的位置调整为free list的头端
	((obj*)p)->next = freeStore;
	freeStore = (obj*)p;
}

在这里插入图片描述
freeStore在新创建出的free list的一开始的指向!

四、macro for static allocator(per-class allocator 4)

在这里插入图片描述

由第三版本知,其黄色部分我们想将其定义为宏操作,进一步简化代码内容:

// DECLARE_POOL_ALLOC  used in class definition
#define DECLARE_POOL_ALLOC()\
public:\
	void* operator new(size_t size){
		return myAlloc.cllocate(size);
	}\
	void operator delete(void* p){
		myAlloc.deallocate(p, 0);
	}\
protected:\
	static allocator myAlloc;

// IMPLEMENT_POOL_ALLOC   used in class implementation file
#define IMPLEMENT_POOL_ALLOC(class_name)\
	allocator class_name::myAlloc;

使用实例如图所示:
在这里插入图片描述
在类内进行宏声明,在类外进行宏定义【告诉编译器传入参数的class type】!

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值