空间配置与释放, std::alloc

本文深入解析SGI STL内存配置器的工作原理,包括双层级配置器的设计,第一级配置器使用malloc()和free()处理大区块,第二级配置器通过内存池管理小区块,避免内存碎片,提高配置效率。

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

C++的内存配置基本操作是 ::operator new( ),内存释放基本操作是 ::operator delete().这两个全局函数相当于C的malloc()和free()函数.正是如此,SGI正是以malloc()和free()完成内存的配置与释放.
考虑到小型区块的可能造成的内存破碎问题,SGI设计了双层级配置器,第一级配置器直接使用malloc()和free(),第二级配置器则视情况而定.当配置区块大于128bytes时,视之为足够大,调用第一级配置器;当配置区块小于128bytes时,视之为过小则调用第二级配置器,
整个设计究竟只开放第一级配置器还是同时开放第二级配置器,取决于__USE__MALLOC是否被定义了.

typedef __malloc__alloc_template<0>malloc_alloc;
  3 typedef malloc_alloc alloc;//令malloc为第一级配置
  4 typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS,0>alloc;//此时为第二级配置器 

__malloc_alloc_template是第一级配置器,__default_alloc_template是第二级配置器
__USE_MALLOC 被定义为yes
SGI STL第一级配置器
template
class __malloc_alloc_template{…}
其中:
1,allocate()直接使用malloc() deallocate()直接使用free()
2,模拟C++的set_new_handler()以处理内存不足的情况
__USE_MALLOC 被定义为no
SGI STL第二级配置器
template<bool threads,int inst>
class __default_alloc_template{…};
其中
1,维护16个自由链表(free lists)
负责16种小型区块的次配置能力.内存池(memory pool)以malloc()配置而得 如果内存不足,转调用第一级配置器.
2.如果需求区块大于128bytes则转调用第一级配置器.

第一级配置器 __malloc_alloc_templatejeixi

#if 0
#include<new>
# define _THROW_BAD_ALLOC throw bad_alloc
#elif !defined(_THROW_BAD_ALLOC)
#include<iostream.h>
#define _THROW_BAD_ALLOC cerr<<"out of memory"<<endl;
exit(1);
#endif
template <int inst>
class __malloc_alloc_template{
	private:
		//out-of-memory
		static void *oom_malloc(size_t);
		static void *oom_realloc(void *,size_t);
		static void (* __malloc_alloc_oom_handler)();
public:
	static void * allocate(size_t n){
		void *result=malloc(n); //第一级配置器直接使用malloc()
	//以下满足需求时 改用oom_alloc(n)
	if(0==result) result==oom_malloc(n);
	return result;
}
static void deallocate(void *p,size_t){
	//释放内存
	free(p);//第一空间适配器直接使用free  
}
	//重新分配内存
static void *reallocate(void *p,size_t,size_t new_sz){
	void *result=realloc(p,new_sz);//第一级配置器直接使用realloc()
	//realloc()函数的原型 extern void*realloc(void *mem_address,unsigned int newsize)  指针名=(数据类型*)realloc(要改变内存大小的指针名,新的大小)
	if(0==result) result=oom_realloc(p,new_sz);//报错之后会重新配置
	return result;
}
//仿照C++的set_new_handler(),换句话说,可以指定自己的out-of-memory handler
static void (*set_malloc_handler(void (*f)()))()
{
	void (*old)()=__malloc_alloc_oom_handler;
	return (old);
}
};
//malloc_alloc out_of-memory handling 初值的设定
//模板的非类型参数只能是整型常量(包括enum),或者指向外部链接的指针(包括函数指针,类的成员函数指针)
template<int inst>
void (*__malloc_alloc_template<inst>::__malloc_alloc_oom_handler)()=0;
//也可以这么使用
//typedef __malloc_alloc_template<0>malloc_alloc
template<int inst>
void *__malloc_alloc_template<int >::oom_malloc(size_t n)
{
	void (*my_malloc_handler)();
	void *result;
	for(; ;){//不断尝试释放,配置
	my_malloc_handler=__malloc_alloc_oom_handler;
	if(0==my_malloc_handler){__THROW_BAD_ALLOC;}//如果成立,则跑出异常
	(*my_malloc_handler)();//调用处理例程,企图释放内存	
         result=malloc(n);//再次尝试配置内存
	if(result) return result;
	
}
}
template<int inst>
void *__malloc_alloc_template<inst>::oom_realloc(void *p,size_t n)
{
	void (*my_malloc_handler)();
	void *result;
	for(; ;){
	my_malloc_handler=__malloc_alloc_oom_handler;
	if(0==my_malloc_handler){__THROW_BAD_ALLOC;}
	(*my_malloc_handler)();//调用例程 企图释放内存
	reslut=realloc(p,n);//再次尝试配置内存
	if(result) return result;
}
}

第一级配置器以malloc(),free(),realloc()等c函数执行实际的内存配置,释放,重配置操作,出现类似C++ new-handler机制,但是它不是直接使用new-handler机制,因为没有使用::operator new来配置内存.
C++ new handler机制是 要求系统在内存配置需求无法被满足时调用一个你指定的函数.换句话说,一旦::operator new无法完成任务,在丢出std::bad_alloc之前 ,会先调用客端指定的处理例程.
SGI第一级配置器allocate()和realloc()都是在调用malloc()和realloc()不成功后,改用oom_malloc和oom_realloc().两者都有内循环,不断调用"内存不足处理例程",期望在某次调用后,获得足够的内存而圆满完成任务.但是如果客端没有设定处理"内存不足历程",那么这两个oom函数就会调用__THROW_BAD_ALLOC,丢出bad_alloc异常信息.
第二级配置器 __default_alloc_template剖析
第二级配置器多了一些机制,避免太多小额区块造成内存的碎片,小额区块带来的不仅是内存碎片,配置时额外的负担也是一个问题.区块越小,额外的负担所占的比例就越大,就显得更加浪费.
第二级配置器的做法是,如果区块足够大,超过128 bytes,就移交第一级配置器处理;如果小于128字节是,就交给内存池处理.每次配置一大块内存,并维护对应之自由链表(free-list),下次再有相同大小的内存需求,就直接从自由链表中拔出.如果客户端释放小额区块由配置器回收到free-list中.为了管理方便,SGI第二级配置器将小额区块的内存上调至8的倍速.

union obj {
	union obj *free_list_link;  //此时obj可以视为一个指针 指向相同形式的另一个obj
		char client_data[1];//可以被视为一个指针  指向一个区块
};

第二级配置器的实现

enum{__ALIGN=8};//小型区块的上调边界
enum{__MAX_BYTES=128};//小型区块的上限
enum{__NFREELISTS=__MAX_BYTES/__ALIGN};//free_list的个数  一共是16个
//以下是第二级配置器
//第一参数用于多线程 
template<bool threads,int inst>
class __default_alloc_template{
	private:
		//ROUND_UP将bytes上调至8的倍数
		static size_t ROUND_UP(size_t bytes){
			return (((bytes)+ALIGN-1)&~(ALIGN-1));
}
	private:
		union obj{//free-lists的节点构造
	union obj *free_list_link;
	char client_data[1];
};
	private:
		//16个free-lists  在前面的计算得来的  
		static obj *volatile free_list[__NFREELISTS];
		//以下函数根据区块大小 决定使用第n号free-list,n从0开始
		static size_t FREELIST_INDES(size_t bytes){
			return (((bytes)+__ALIGN-1)/__ALIGN-1);
}
//返回一个大小为n的对象,并可能加入大小为n的其他区块到free list
	static void *refill(size_t n);
//配置一块大区间,可以容纳nobjs个大小为"size"的区块
static char *chunk_alloc(size_t size,int &nobjs);
//chunk allocation state
static char *start_free;//内存池的起始位置,只在chunk_alloc()中变化
static char *end_free;//内存池的结束位子
static  size_t heap_size;
public:
	static void *allocate(size n){}
	static void deallocate(void *p,size_t n){}
	static void *reallocate(void *p,size_t old_sz,size_t new_sz);
};
//以下是static data number的定义与初值的设定
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>
char *__default_alloc_template<threads,inst>::heap_size=0;
 template<bool threads,int inst>
__default_alloc_template<threads,inst>::obj *volatile
__default_alloc_template<threads,inst>::free_list[__NFREELISTS]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}

//空间配置函数allocate()
//此函数先判断区块的大小 大于128bytes就调用第一级配置器,小于128就检查对应的free list 如果有可用的区块就直接拿来用  
//如果没有 那么就将区块的大小上调至8倍数边界 然后调用 refill() 准备为free list重新配置空间
static void *allocate(size_t n)//n必须大于0
{
	obj *volatile *my_free_list;
	obj *result;
	//大于128就调用第一级配置器
	if(n>(size_t)__MAX_BYTES){
		return (malloc_alloc::allocate(n));//此时这个函数就直接返回了
}
	//寻找16个free list中适当的一个
	my_free_list=free_list+FREELIST_INDEX(n);
	result=*my_free_list;
	if(result==0){
	//没有可用的free list  准备填充新的free list
	void *r=refill(ROUND_UP(n)); //在原先的基础上再加8
	return r;
}	
	//调整free list
	*my_free_list=result->free_list_link;
	return (result);
}
//空间释放函数
//同样的道理  大于128bytes就调用第一级配置器 小于128就找出对应位置free list 将其回收
static void deallocate(void *p,size_t n)//p不是0
{
	obj *q=(obj *)p;
	obj *volatile *my_free_list;
	//大于128就调用第一级配置器
	if(n>(size_t)__MAX_BYTES){
		malloc_allocate(p,n);
		return ;
}
	//寻找对应的free list
	my_free_list=free_list+FREELIST_INDEX(n);
	q->free_list_link=*my_free_list;
	*my_free_list=q;
}
template <bool threads,int inst>
void *__default_alloc_template<threads,inst>::refill(size_t n)
{
	int nobjs=20;
	//调用chunk_alloc(),尝试取得nobjs个区块作为free list的新节点
	char *chunk=chunk_alloc(n,nobjs);
	obj *volatile *my_free_list;
	obj* result;
	obj *current_obj,*net_obj;
	int i;
	//如果只获得一个区块,这个区块就分配给调用者,free list无新节点
	if(1==nobjs) return (chunk);
	//否则准备调整free list,纳入新结点
	my_free_list=free_list+FREELIST_INDEX(n);
	//以下在chunk空间建立free list
	result=(obj* )chunk;//这一段准备返回给客端
	//以下导引free list指向新配置的空间(取自内存池)
	*my_free_list=next_obj=(obj *)(chunk+n)
	//以下将free list 的各结点串接起来
	for(i=1; ;i++){//从1开始,因为第0个将返回给客户端
	current_obj=next_obj;
	next_obj=(obj*)((char *)next_obj+n);
	if(nobjs-1==i){
		current_obj->free_list_link=0;
		break;
}else{
	current_obj->free_list_link=next_obj;
}

}
	return (result);
	
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值