一、空间的配置与释放,std::alloc
对象构造前的空间配置和对象析构后的空间释放,由<stl_alloc.h>负责,SGI对此的设计哲学如下:
1. 向 system heap 要求空间。
2. 考虑多线程(multi-threads)状态。
3. 考虑内存不足时的应变措施。
4. 考虑过多“小型区块”可能造成的内存碎片(fragment)问题。
考虑到小型区块所可能造成的内存破碎问题,SGI设计了双层级配置器,第一级配置器直接使用 malloc() 和 free() ,第二级配置器则视情况采用不同的策略:当配置区块超过 128 bytes时,视之为“足够大”,便调用第一级配置器;当配置区块小于 128 bytes时,视之为“过小”,为了降低额外负担,便采用复杂的 memory pool 整理方式,而不再求助于第一级配置器。
在SGI的整个设计究竟只开放第一级配置器,或是同时开放第二级配置器,取决于 __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 就是第二级配置器。SGI STL并未定义 __USE_MALLOC,所以SGI使用第二级配置器。
SGI 还为 alloc 包装了一个接口如下,使配置器的接口能够符合 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)); }
};
其内部四个成员函数其实都是单纯的转调用,调用传递给配置器的成员函数。SGI STL容器全部使用这个 simple_alloc 接口。
二、第一级配置器 __malloc_alloc_template 剖析
// 注意,无"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);
#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
static void (* __malloc_alloc_oom_handler)();
#endif
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
#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
// 初值为 0。有待客端设定
template <int inst>
void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0;
#endif
template <int inst>
void * __malloc_alloc_template<inst>::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)(); // 调用处理例程,企图释放内存
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 无法完成任务,在丢出 std::bad_alloc 异常状态之前,会先调用由客端指定的处理例程。)
设计“内存不足处理例程”是客端的责任,设定“内存不足处理例程”也是客端的责任。