对象构造前的空间配置和对象构造后的空间释放
由<stl_alloc.h>
负责,SGI对此的设计哲学如下:
*向system heap 要求空间
*考虑多线程(multi-threads)状态
*考虑内存不足时的应变措施
*考虑过多“小型区块”可能造成的内存碎片(fragment)问题
C++的内存配置基本操作是::operator new()操作,内存释放基本操作是::operator delete()。这两个全局函数相当于C的malloc()和free()函数。SGI正是以malloc()和free()完成内存的配置与释放。
考虑到小型区块可能造成的内存破碎问题,SGI设计了双层级配置器。第一级配置器直接使用malloc()和free(),第二级配置器则视情况采用不同的策略。
*当配置区块超过128bytes时,视之为“足够大”,便调用第一级配置器
*当配置区块小于128bytes时,视之为“过小”,为了降低额外负担,便采用复杂的memory pool整理方式,而不再求助于第一级配置器。
整个设计究竟只开放第一级配置器,或是同时开放第二级配置器,取决于_USE_MALLOC是否被定义:
#ifdef _USE_MALLOC
...
typedef _malloc_alloc_template<0> malloc_alloc;
typedef malloc_alloc alloc;//令alloc 为第一级配置器
#else
...
//令alloc为第二级配置器
typedef _default_alloc_template<_NODE_ALLOCATOR_THREADS,0> alloc;
#endif/*!_USE_MALLOC*/
_malloc_alloc_template就是第一级配置器
_default_alloc_template就是第二级配置器
注意:alloc并不接受任何template型别参数
无论alloc被定义为第一级配置器或第二级配置器,SGI还为它再包装一个接口如下,使配置器的接口能够符合STL规格:
template<class T,class Alloc>
class simple_alloc{
public:
static T *allocate(size_t n) {return 0==n?0:(T*)Alloc::allocate(n*sizeof(T));}
static T *allocate(void) {return (T*)Alloc::allocate(sizeof(T));}
static void deallocate(T *p,size_t n) {if(0!=n)Alloc::deallocate(p,n*sizeof(T));}
static void deallocate(T *p) {Alloc::deallocate(p,sizeof(T));}
};
其内部四个成员函数其实都是单纯的转调用,调用传递给配置器的成员函数。这个接口使配置器的配置单位从bytes转为个别元素的大小(sizeof(T))。SGI STL容器全部使用这个simple_alloc接口,例如:
template<class T,class Alloc=alloc>//缺省使用alloc为配置器
class vector{
protected:
//专属空间配置器,每次配置一个元素大小
typedef simple_alloc<value_type,Alloc> data_allocator;
void dealocate(){
if(...)
data_allocator::deallocate(start,end_of_storage - start);
}
...
};
第一级配置器 __malloc_alloc_template 剖析
#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
//malloc-based allocator通常比稍后介绍的default alloc速度慢
//一般而言是thread-safe,并且对于空间的运用比较高效(efficient)
//以下是第一级配置器
//注意,无"template型别参数"。至于"非型别参数"inst,则完全没有派上用场
template <int inst>
class __malloc_alloc_template{
private:
//以下函数将用来处理内存不足的情况
//oom: 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_malloc()
if(0==result) result=oom_malloc(n);
return result;
}
static void deallocate(void *p,size_t /* n */)
{
free(p);//第一级配置器直接使用free()
}
static void *reallocate(void *p,size_t/*old_sz*/,size_t new_sz)
{
void *result=realloc(p,new_sz);//第一级配置器直接使用realloc()
//以下无法满足需求时,改用oom_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;
__malloc_alloc_oom_handler=f;
return(old);
}
};
//malloc_alloc out-of_memory handling
//初值为0.有待客端设定
template <int inst>
void (*__malloc_alloc_template<inst>::__malloc_alloc_oom_handler)()=0;
template <int inst>
void *__malloc_alloc_template<inst>::oom_malloc(sizt_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=realloc(p,n);
if(result) return(result);
}
}
//注意,以下直接将参数inst指定为0
typedef __malloc_alloc_template<0> malloc_alloc;
第一级配置器以malloc(),free(),realloc()等C函数执行实际的内存配置、释放、重配置等操作,并实现出类似C++ new-handler的机制。但是,他不能直接使用C++ new-handler机制,因为它并非使用::operator new来配置操作。
所谓C++ new-handler机制是,你可以要求系统在内存配置需求无法被满足时调用一个你所指定的函数。即,一旦::operator new无法完成任务,在丢出std::bad_alloc异常状态之前,会先调用由客端指定的处理例程。该处理例程即被称为new-handler。new-handler解决内存不足的做法有特定的模式。
注意,SGI以malloc而非::operator new来配置内存,因此SGI不能直接使用C++的set_new_handler(),必须仿真一个类似的set_malloc_handler()。
请注意,SGI第一级配置器的allocate()和realloc()都是在调用malloc()和realloc()不成功后,改调用oom_malloc()和oom_realloc()。后两者都有内循环,不断调用“内存不足处理例程”,期望在某次调用之后,获得足够的内存而圆满完成任务。但如果“内存不足处理例程”并未被客端设定,oom_malloc()和oom_realloc()便老实地调用__THROW_BAD_ALLOC,丢出bad_alloc异常信息,或利用exit(1)终止程序。