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);
}