一级,二级空间配置器

作为STL的六大组件之一——空间配置器,它的作用就是分配和管理空间,为容器包括算法提供生存空间。然而在我们使用STL中的利器在程序的舞台上大放光彩时,往往看不到它的存在,它总是站在舞 台的背后,为我们精彩的演出默默的付出。今天我们就来看下空间配置器。

 1. 为什么需要空间配置器呢

(1)通常申请的小块内存,容易造成内存碎片,最终造成空间的浪费。

(2)当我们用malloc申请空间时,底层会给我们一个struct结构体来管理这块空间,管理是需要成本的。

(3)每次都向系统要小块的内存,效率太低。

(4)如果使用不当,容易忘记释放空间,造成内存泄漏

(5)提高代码复用率,比如vector和List都有malloc申请空间的重复代码。

2. STL中空间配置器实现的要求

(1)具有空间不足时的应对措施

(2)隐藏实际中对存储空间的分配和释放的细节,确保所有被分配的存储空间最终都可以被释放。

(3)向system heap中要空间。

(4)考虑多线程状态(muilty_thread)。

(5)考虑过多“小型区块”造成的内存碎片问题

3. 实现的细节

                             

(1)当用户申请的空间大于等于128个字节时,使用一级空间配置器。当用户申请的空间小于128个字节时,使用二级空间配置器。

(2)一级空间配置器的实现主要由malloc 为我们申请空间,并提供了空间不足(out_of_memory)时的应对措施——释放不用的空间。

(3)二级空间配置器分为两个部分,一个是内存池(直接向系统堆中申请内存),一个是维护16个自由链表,负责16种小型区块的配置能力。

(4)在二级空间配置器中,不管用户申请的空间是多少个字节,根据一个索引函数,将用户申请的字节数上调至8的整数倍,没每次都分配给用户8的整数倍个字节。

(5)二级空间配置器的主要实现过程

                                                    

4. 代码的具体实现

   (1)一级空间配置器

typedef void(*pmalloc_oom_handle)();              //定义函数指针类型
template <int inst>
class malloc_alloc_template{

private:
	static pmalloc_oom_handle _mallocHandle;      //静态成员变量
public:
	static void* allocate(size_t size)
	{
		__TRACE_DEBUG("一级空间配置器:%d \n", size);
		void* result = malloc(size);         //直接malloc 开辟空间
		if (result == 0)
			result = oom_malloc(size);     //oom_malloc空间不足的应对措施
		return result;
	}
	static void deallocate(void* ptr, size_t size)
	{
		__TRACE_DEBUG("一级空间配置器释放空间:%d\n ", size);
		free(ptr);                      //直接调用free释放空间
	}
	static void* Reallocate(void* ptr, size_t newsize,size_t oldsize)
	{
		void* result = realloc(newsize);
		if (result == 0)
			result = oom_realloc(ptr,newsize);
		return result;
	}
	static void dereallocate(void*ptr, size_t size)
	{
		free(ptr);
	}
private:
	static void* oom_malloc(size_t size)
	{
		__TRACE_DEBUG("一级空间配置器,内存不足oom:%d \n", size);
		pmalloc_oom_handle my_malloc_handle;
		void* result;
		for (;;)
		{
			my_malloc_handle = _mallocHandle;
			if (my_malloc_handle == NULL)              //如果在空间不足时没有定义应对措施处理函数,就抛异常。
			{
				throw std::bad_alloc();
			}
			//尝试去释放已经获取但是不用的空间
			my_malloc_handle();
			result = malloc(size);
			if (result)
			return result;
		}
	}
	pmalloc_oom_handle set_malloc_oom_handle(pmalloc_oom_handle mallocHandle)
	{
		pmalloc_oom_handle old = _mallocHandle;
		_mallocHandle = mallocHandle;
		return (old);
	}
};
//类外初始化静态成员变量
pmalloc_oom_handle malloc_alloc_template<0>::_mallocHandle = NULL;

(2)二级空间配置器

template<int inst>
class DefaultAllocateTemplate
{
public:
	static void* Allocate(size_t size)
	{
		if (size > 128)
			return malloc_alloc_template<0>::allocate(size);   //大于128调用一级空间配置器

		__TRACE_DEBUG("二级空间配置器:%d\n ", size);
		size_t index = freeListindex(size);                     
		if (_freeList[index] == NULL)                            //链表中没有空间,调用refill
			return Refill(ROUND_UP(size));

		void* result = _freeList[index];                     //链表空间充足,返回给用户
		_freeList[index] = _freeList[index]->_freeListLink;
		return result;
	}
  static void Deallocate(void* ptr,size_t size)
	{
		if (size > 128)
			return malloc_alloc_template<0>::deallocate(ptr,size);
		__TRACE_DEBUG("二级空间配置器释放:%d \n", size);
		size_t index = freeListindex(size);            //二级空间配置器并不是真正的释放空间,而是将内存块归还至链表
		OBJ* cur = (OBJ*)ptr;
		cur->_freeListLink = _freeList[index];
		_freeList[index] = (OBJ*)ptr;

	}
	private:
		static void* Refill(size_t size)
		{
			__TRACE_DEBUG("二级空间配置器refill:%d\n ", size);
			int objs = 20;
			char* chunk = (char*)chunk_alloc(size, objs);       //调用chunk_alloc函数向内存池中找

			if (objs == 1)
				return chunk;

			size_t index = freeListindex(size);
			OBJ* cur = (OBJ*)(chunk + size);
			while (--objs)
			{
				cur->_freeListLink = _freeList[index];    //头插法将剩余空间连接在index位置
				_freeList[index] = cur;
				cur = (OBJ*)((char*)cur + size);
			}
			return chunk;
		}
		static void* chunk_alloc(size_t size, int& objs)
		{
			size_t NeedSize = size*objs;    //需要的objs个字节数
			size_t LeaveSize = _startFree - _endFree; //剩余字节数
			char* result;    //返回的字节数

			if (LeaveSize >= NeedSize)
			{
				__TRACE_DEBUG("二级空间配置器,内存池空间足够,返回%d个%d字节的内存块\n ", objs, size);
				result = _startFree + NeedSize;
				return result;
			}
			else if (LeaveSize >= size)
			{
				size_t n = LeaveSize / size;
				__TRACE_DEBUG("二级空间配置器,内存池不太够,返回%d个%d字节的内存块\n ", n, size);
				
				objs = n;
				result = _startFree + n*size;
				return result;
			}
			else
			{
				
				size_t index = freeListindex(LeaveSize);
				if (LeaveSize > 0)
				{
					OBJ* cur = (OBJ*)_startFree;
					cur->_freeListLink = _freeList[index];
					_freeList[index] = cur;

				}

				size_t Getbytes = 2 * NeedSize + ROUND_UP(_heapsize >> 4);
				__TRACE_DEBUG("二级空间配置器,内存池不够了,%d字节的内存块 \n", Getbytes);
				_startFree = (char*)malloc(Getbytes);  //向堆中申请
				if (_startFree == 0)
				{
					//如果在堆中申请失败,先在链表中找
					__TRACE_DEBUG("二级空间配置器,内存池不够了,在堆中找失败,\
						   						  在链表中找, %d字节的内存块\n ", (index + 1)*_ALIGN);
					int index = freeListindex(size);
					for (int i = index; i < _NFREELISTS; i++)
					{
						OBJ* cur = _freeList[index];
						if (cur != NULL)
						{
							_startFree = (char*)cur;
							_freeList[index] = cur->_freeListLink;
							_endFree = _startFree + (index + 1)*_ALIGN;
							return chunk_alloc(size,objs);

						}
					}

					//如果链表中没有,则在一级空间配置器中找
					__TRACE_DEBUG("二级空间配置器,内存池不够了,在堆中找失败,\
						   	在链表中找, %d字节的内存块\n ", Getbytes);
					_endFree = 0;
					_startFree = (char*)malloc_alloc_template<0>::allocate(Getbytes);
				
				}
				__TRACE_DEBUG("二级空间配置器,内存池不够了,在堆中找成功,\
					   %d字节的内存块\n ", Getbytes);
				_heapsize += Getbytes;
				_endFree = _startFree + Getbytes;
				return (chunk_alloc(size, objs));
			}

		}
private:
	static size_t ROUND_UP(size_t bytes)       //将申请的空间上调至8的整数倍
	{
		return (((bytes)+_ALIGN - 1)&~(_ALIGN - 1));
	}
	static size_t freeListindex(size_t bytes)  //计算索引函数
	{
		return (((bytes)+_ALIGN - 1) / _ALIGN - 1);
	}
private:
	enum {_ALIGN=8};   //每次调整的字节数
	enum{_MAX_BYTES=128};   //
	enum { _NFREELISTS = _MAX_BYTES / _ALIGN };   //数组的大小

	union OBJ
	{
		OBJ* _freeListLink;
		char clientData[1];
	};
private:
	static char* _startFree;  //标记空间的起始地址
	static char* _endFree;       //标记空间的结束地址
	static size_t _heapsize;     //向堆中申请的字节数
	static OBJ* _freeList[_NFREELISTS];    //存放OBJ的指针
};

//静态成员变量类外初始化
template <int inst>
char*  DefaultAllocateTemplate<inst>::_startFree = NULL;

template <int inst>
char*  DefaultAllocateTemplate<inst>::_endFree = NULL;

template <int inst>
size_t  DefaultAllocateTemplate<inst>::_heapsize = NULL;

template <int inst>
typename DefaultAllocateTemplate<inst>::OBJ*  DefaultAllocateTemplate<inst>::\
             _freeList[DefaultAllocateTemplate<inst>::_NFREELISTS] = { 0 };

(3)为了让用户使用方便,我们将一级空间配置器和二级空间配置器进行封装

#ifdef USE_MALLOC
typedef malloc_alloc_template<0> _Alloc;
#else
typedef DefaultAllocateTemplate<0> _Alloc;
#endif

template <class T,class Alloc>
class Simpleallocate
{
public:
	static T* allocate(size_t n) //申请n个类型为T的字节大小
	{
		return 0 == n ? 0 : (T*)(_Alloc::Allocate(n*sizeof(T)));
	}
	static T* allocate(void)   //申请T字节大小
	{
		return (T*)(_Alloc::Allocate(sizeof(T)));
	}
	static void deallocate(T* p, size_t n)
	{
		if (n!=0)
		 _Alloc::Deallocate(p, n*sizeof(T));
	}
	static void deallocate(T*p)
	{
		_Alloc::Deallocate(p, sizeof(T));
	}
};

(4)我们调用一个特殊的函数对我们写的空间配置器进行调试

#include <stdarg.h>
#define _DEBUG_
static string GetFileName(const string& path) 
{        
	char ch = '/' ; 
   #ifdef _WIN32       
    ch = '\\';
   #endif
   size_t pos = path.rfind(ch);        
   if (pos == string::npos)                
   return path;        
   else
   return path.substr(pos + 1);
}

inline static void _trace_debug(const char * funcName, const char * fileName, int line, char* format, ...) 
{
    #ifdef _DEBUG_
	fprintf(stdout, "[%s:%d]%s", GetFileName(fileName).c_str(), line, funcName);
	// 输出用户信息        
	va_list args;        
	va_start(args, format);        
	vfprintf(stdout, format, args);       
	va_end(args); 
   #endif
}
#define __TRACE_DEBUG(...) _trace_debug(__FUNCDNAME__, __FILE__, __LINE__, __VA_ARGS__);

二级空间配置器的实现还是有点复杂的,需要理清思路,明白原理。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值