STL实现简单的空间配置器

Posted 巴山雨夜

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STL实现简单的空间配置器相关的知识,希望对你有一定的参考价值。

1、模拟实现STL源码中实现的空间配置器

实现方式: STL源码中的空间配置器的实现分为一级二级空间配置器

1.1 一级空间配置器

所谓的一级空间配置器:(实现特点) 【1】直接使用malloc函数申请内存 【2】自定义清除函数;(当malloc失败之后,调用清除函数来释放一部分的内存空间,以便malloc的成功) 【3】假设调用清除函数之后,还是malloc失败的话,就需要抛出异常 (bad_alloc) 代码实现:
//实现一级空间配置器
template<int inst>
class  __Malloc_Alloc_Template

public:
	static  void* allocate(size_t n)
	
		void  *  result = malloc(n);
		//要是malloc失败
		if(result == NULL)
			
			//调用omm_malloc函数
			result = omm_malloc(n);
		
		return result;
	
	//空间释放
	static  void  deallocate(void * del)
	
		//直接调用free
		free(del);
	

	//设置自定义的清理函数 函数返回的是原来的清理函数
	//函数原型的意思就是 
	//定义一个函数 返回值是 静态的函数指针参数为空
	//这个函数的参数为   函数指针参数 为 空 func
	static void  (*set_malloc_handler(void (* func)()))()
	
		void  (*old)  = ___malloc_alloc_oom_handler;
		___malloc_alloc_oom_handler = func;
		return old;
	
private:
	static void (*___malloc_alloc_oom_handler)();
	//表示当前malloc失败内存不足,调用自定义的清理函数
	static void * omm_malloc(size_t n)
	
		void (*my_malloc_handler)();
		void  *result =NULL;
		while(1)
		
			my_malloc_handler =	___malloc_alloc_oom_handler;
			//表示的当前的清理函数为空的话
			if(my_malloc_handler == NULL)
			
				//直接抛出异常
				throw bad_alloc();
			
			//调用清理函数
			my_malloc_handler();
			//调用完之后重新malloc来申请空间
			result = malloc(n);
			if(result)
			
				return  result;
			
		
	
;
template<int inst>
void (*__Malloc_Alloc_Template<inst>::___malloc_alloc_oom_handler)()=NULL;

1.2 二级空间配置器

实现方式: 在这里引入内存池与自由链表来实现二级空间配置
二级空间配置器的优缺点:

【内存池】

我们都知道,编译器在用户态与内核态之间的转换是十分的耗时的。 所以,我们可以事先申请一大片的内存空间;将它保存起来,等到需要动态开辟空间的时候,直接到这块空间上切一块, 直到用完之后,重新申请;

【自由链表】

何为自由链表? 就是使用链表将所需要大小的内存块链到一块。                                        
SGI二級空间配置器保存的自由链表的空间块大小都是8的倍数;并且会將任何小区域的内存块的大小調至8 的倍數(
各自管理大小分別為 8, 16, 24, 32, 40, 48, 56, 64, 72,80, 88, 96, 104, 112, 120, 128 bytes, 当需要的空间大小超过128byte时,直接调用一级空间配置器。
实现代码:
//实现二级空间配置器
template<bool threads,int  inst>
class __Default_Alloc_Template

public:	
	static void  *  allocate(size_t n)
		
		//要是当前要开辟的空间大于128的则直接使用 一级空间配置器来malloc开辟
		if(n >(size_t) __MAX_BYTES)
		
			return __Malloc_Alloc_Template<0>::allocate(n);
		
		//小于128的直接到自由链表中获取
		//index:得到大小为n的空间要在自由链表的下标为index处获得
		size_t  index = FREELIST_INDEX(n);
		//result表示的就是自由链表的第一结点;
		obj* result = free_list[index];
		if(result== NULL)
		
			//要是当前result 为NULL 表示的当前的自由链表中没有当前大小的内存块
			//需要从内存池中进行切分
			return  refill(ROUND_UP(n));
		
		//表示的当前的自由链表中有此大小的内存块;直接拿到;
		//更新当前的自由链表
		free_list[index] = result->free_list_link;
		return  result;
	
	//refill函数表示的是要是当前的自由链表中没有此大小的内存块
	//需要来从内存池中来切分
	static void  * refill(size_t n)
	
		//表示现在需要到内存池中切分20个这样大小的内存块
		size_t nobjs= 20;
		//chunk_alloc为切分函数 
		char  * chunk = chunk_alloc(n,nobjs);
		//nobjs表示输出函数 返回的是实际切分到的内存块个数
		if(nobjs == 1)
		
			//要是只切分到一个内存块的话,直接返回就好
			return  (void *) chunk;
		
		//其他就表示的是得到的内存块的个数大于1
		//需要将剩余的内存块挂接到的自由链表中
		size_t  index = FREELIST_INDEX(n);
		//cur表示的就去除第一个要返回的内存块,的第二个内存块
		obj *  cur = (obj*)chunk +  n;
		free_list[index] = cur;
		for(size_t  i = 2;i < nobjs;++i)
		
			obj * next = (obj*)(chunk + i*n);
			cur->free_list_link = next;
			cur= next;
		
		cur->free_list_link = NULL;
		return (void *)chunk;
	
	//chunk_alloc函数来从内存池中切分大小为size的内存块 需要的个数为nobjs
	//nobjs传引用 ,返回的是实际切分的内存块个数
	static char * chunk_alloc(size_t size,size_t & nobjs)
	
		char * ret  = NULL;
		size_t total_bytes = size * nobjs;//表示总共需要切分的内存大小
		size_t bytes_left = end_free - start_free;//表示的是当前的内存池的大小

		if(bytes_left > total_bytes)
		
			//表示的是当前的内存池足够切分当前需要的内存块
			ret = start_free;
			start_free += total_bytes;//更新内存池的大小
			return ret;
		
		else if(bytes_left >=size)
		
			//表示的是当前的内存池内不足以开辟当前需要的内存块 
			//但足够开辟至少一个内存块
			nobjs = bytes_left /size;//表示的当前切分到的内存块个数
			ret =start_free ;
			start_free +=  (nobjs*size);
			return ret;
		
		else
		
			//表示的是当前的内存池一个内存块都不够切分
			//则需要重新malloc来开辟空间
			if(bytes_left != 0)//当前的内存池大小不为0 ,需要处理剩余的内存
			
				//将当前内存池中的空间合理的分配得到自由链表中
				int index =  FREELIST_INDEX(bytes_left);
				//头插到到自由链表中
				((obj*)start_free)->free_list_link = free_list[index];
				free_list[index] = (obj*)start_free;
			
			//重新开辟新空间
			size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size>>4);//bytes_to_get表示新开辟空间的大小
			start_free = (char*)malloc(bytes_to_get);
			if(start_free == NULL)
			
				//表示的是malloc失败,系统已经开不出空间
				//现在只能从自由链表中来看看
				for(size_t  i = FREELIST_INDEX(size);i < (size_t)__NFREELISTS;++i )
				
					if(free_list[i] != NULL)
					
						start_free = (char*)free_list[i];
						end_free = start_free + (i+1)*__ALIGN;
						return  chunk_alloc(size,nobjs);
					
				
				end_free =  0 ;
				start_free = (char*)__Malloc_Alloc_Template<0>::allocate(bytes_to_get);
			
			heap_size = heap_size+ bytes_to_get;
			end_free = start_free + bytes_to_get;
			return chunk_alloc(size,nobjs);
		
	
	static void  deallocate(void  * p,size_t  n)
	
		obj * 	del =(obj*) p;
		//要是当前释放的空间大小大于128直接使用一级空间配置器释放
		if( n >(size_t)__MAX_BYTES )
		
			__Malloc_Alloc_Template<0>::deallocate(p);
			return  ;
		
		//将释放的空间连到自由链表上
		size_t index = FREELIST_INDEX(n);
		del->free_list_link = free_list[index];
		free_list[index] = del;
	
private:
	//表示的小型的区域的上调边界
	enum __ALIGN = 8;
	//自由链表的链接的内存的最大值
	enum __MAX_BYTES =128;
	//表示的是自由链表的个数
	enum __NFREELISTS = __MAX_BYTES / __ALIGN;
	//ROUND_UP函数将bytes上调到 8 的倍数
	static size_t ROUND_UP(size_t  bytes)
	
		//任何数+ 7 将这个数 与上 FFF0
		return (bytes +__ALIGN-1)& ~(__ALIGN-1);
	
	//根据要开辟的空间大小来得到 该大小在自由链表中的下标
	static size_t  FREELIST_INDEX(size_t bytes)
	
		return (bytes + __ALIGN-1)/(__ALIGN-1);
	
private:
	//链表的结点
	union obj
	
		union obj *  free_list_link ;//自由链表
		char client_data[1];
	;
	//自由链表数组 ,内部保存一个个的内存块链表
	static  obj * free_list[__NFREELISTS];
	static char*  start_free;//内存池的起点
	static char*  end_free;//内存池的结尾
	static size_t   heap_size ;//内存池的大小;
	
;


template<bool threads,int inst>
char  *__Default_Alloc_Template<threads,inst>::start_free = 0;
template<bool threads,int inst>
char  *__Default_Alloc_Template<threads,inst>::end_free = 0;
template<bool threads,int inst>
typename __Default_Alloc_Template<threads,inst>::obj*  __Default_Alloc_Template<threads,inst>::free_list[__Default_Alloc_Template<threads,inst>::__NFREELISTS] = 0;
template<bool threads,int inst>
size_t __Default_Alloc_Template<threads,inst>::heap_size = 0;

2、空间配置器的包装与运用方式


实现:
#ifdef  __USE_MALLOC
typedef __Malloc_Alloc_Template<0> alloc;
#else
typedef  __Default_Alloc_Template<false,0> alloc;
#endif //__USE_MALLOC

template<class  T ,class  Alloc =alloc>
class simple_alloc

public:
	//n表示的是申请T类型的空间的个数
	static T  * allocate(size_t   n)
	
		return 0==n ? NULL : (T *)Alloc::allocate(n*sizeof(T));
	
	//表示申请一个T类型的空间
	static T *  allocate(void)
	
		return  (T*)Alloc::allocate(sizeof(T));
	
	static void deallocate(T *  del)
	
		Alloc::deallocate(del,sizeof(T));
	
	static void deallocate(T *  del,size_t  n)
	
		if( n!=0)
			Alloc::deallocate(del,sizeof(T)*n);
	
;


#include<vector>
void  Test()

	int * array = simple_alloc<int,alloc>::allocate(2);
	array[0]=0;
	array[1]=1;
	cout<<array[0]<<endl;
	cout<<array[1]<<endl;



以上是关于STL实现简单的空间配置器的主要内容,如果未能解决你的问题,请参考以下文章

STL实现简单的空间配置器

STL空间配置器解析和实现

STL空间配置器

STL源码剖析 — 空间配置器(allocator)

STL空间配置器那点事

STL二级空间配置器的实现