一 简介
虑到小型区块可能造成的内存破碎问题,SGI 为此设计了双层级配置器。当配置区块超过 128bytes 时,称为足够大,使用第一级配置器,直接使用 malloc() 和 free()。当配置区块不大于 128bytes 时,为了降低额外负担,直接使用第二级配置器,采用复杂的 memory pool 处理方式。
二 第一级配置器部分源码
整个设计究竟只开放第一级配置器,或是同时开放第二级配置器,取决于宏 __USE_MALLOC 是否被定义。
# ifdef __USE_MALLOC
...
/*__malloc_alloc_template 就是第一级配置器*/
typedef __malloc_alloc_template<0> malloc_alloc;
typedef malloc_alloc alloc;
#else
...
/*__default_alloc_template 就是第二级配置器*/
typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc;
#endif
无论 alloc 被定义为第一级或第二级配置器,SGI 还为它再封装一个接口如下,使配置器的接口能够符合STL规格
template<class _Tp, class _Alloc>
class simple_alloc {
public:
static _Tp* allocate(size_t __n)
{
return 0 == __n ? 0 : (_Tp*)_Alloc::allocate(__n * sizeof (_Tp));
}
static _Tp* allocate(void)
{
return (_Tp*)_Alloc::allocate(sizeof (_Tp));
}
static void deallocate(_Tp* __p, size_t __n)
{
if (0 != __n) _Alloc::deallocate(__p, __n * sizeof (_Tp));
}
static void deallocate(_Tp* __p)
{
_Alloc::deallocate(__p, sizeof (_Tp));
}
};
上面这内部四个成员函数其实都是单纯的转调用,调用传递给配置器的成员函数。SGI STL 容器全部使用这个simple_alloc 接口。
下面是简化后的一级配置器伪代码
template <int __inst>
class __malloc_alloc_template
{
private:
static void*_S_oom_malloc(size_t); //out of memory时malloc
static void*_S_oom_realloc(void*, size_t); //out of memory 时realloc
#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
static void (*__malloc_alloc_oom_handler)(); //out of memory时处理函数。
//这个函数我们可以自己设置,通过调用__set_malloc_handler
#endif
public:
static void*allocate(size_t __n) //malloc一块空间
{
void*__result = malloc(__n); //__result指向这块空间
if (0 ==__result) __result = _S_oom_malloc(__n); //如果内存不够时。
return__result;
}
static voiddeallocate(void* __p, size_t /* __n */) //释放空间
{
free(__p);
}
static void*reallocate(void* __p, size_t /* old_sz */, size_t __new_sz) //realloc
//一块空间,同样也是在realloc失败时调用_S_oom_realloc。
{
void*__result = realloc(__p, __new_sz);
if (0 ==__result) __result = _S_oom_realloc(__p, __new_sz);
return__result;
}
//设置类似与c++ new handler的内存不足的处理程序
static void (*__set_malloc_handler(void (*__f)()))()
{
void (*__old)() = __malloc_alloc_oom_handler;
__malloc_alloc_oom_handler = __f;
return(__old);
}
};
allocate()
#define __THROW_BAD_ALLOC fprintf(stderr, "out of memory\n"); exit(1)
static void* allocate(size_t __n)
{
void* __result = malloc(__n); //调用malloc()分配内存,向 system heap 要求空间
if (0 == __result) __result = _S_oom_malloc(__n); //malloc分配失败,调用_S_oom_malloc()
return __result; //oom means "out of memory"
}
#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
template <int __inst>
void(*__malloc_alloc_template<__inst>::__malloc_alloc_oom_handler)() = 0;
#endif
//内存不足处理例程,初值为0,待用户自定义,考虑内存不足时的应变措施。
template <int __inst>
void*
__malloc_alloc_template<__inst>::_S_oom_malloc(size_t __n)
{
void(*__my_malloc_handler)(); //函数指针
void* __result;
for (;;) {
//不断的尝试释放、配置、再释放、再配置……
__my_malloc_handler = __malloc_alloc_oom_handler;
/*由于初值设定为0,如果用户没有自定义相应的内存不足处理例程,那么还是抛出异常*/
if (0 == __my_malloc_handler) {
__THROW_BAD_ALLOC; }
(*__my_malloc_handler)(); //用户有自定义(释放内存),则进入相应的处理程序
__result = malloc(__n);
if (__result) return(__result);
}
//不断的尝试释放和配置是因为用户不知道还需要释放多少内存来满足分配需求,只能逐步的释放配置
}
reallocate()
static void* reallocate(void* __p, size_t /* old_sz */, size_t __new_sz)
{
void* __result = realloc(__p, __new_sz);
if (0 == __result) __result = _S_oom_realloc(__p, __new_sz);
return __result;
}
template <int __inst>
void* __malloc_alloc_template<__inst>::_S_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);
}
}
deallocate()
static void deallocate(void* __p, size_t /* __n */)
{
free(__p); //第一级配置器直接使用free()
}
set_new_handler()
第一级配置器以 malloc(),free(),realloc() 等 C 函数执行实际的内存配置、释放、重配置操作,因此,SGI 不能直接使用 C++ 的 set_new_handler(),必须仿真一个类似的 set_malloc_handler()。
/*该函数接收一个返回值为空,参数为空的函数指针作为参数,最后返回一个返回值和参数均为空的函数指针*/
static void (* __set_malloc_handler(void (*__f)()))()
{
void (* __old)() = __malloc_alloc_oom_handler; //保存原有处理例程
__malloc_alloc_oom_handler = __f; //重新指定异常处理例程
return(__old);
}
这个函数重新指定了内存分配异常处理函数,并返回原有的内存分配异常处理函数,即设置新处理例程的同时也保存了原有的处理例程。回看源码中的 allocate() 和 reallocate() 函数中的用户定义内存不足异常处理例程正是通过这里来指定的。
三 总结
SGI 第一级配置器的 allocate() 和 reallocate() 都是在调用 malloc() 和 realloc() 不成功后,改调用 _S_oom_malloc() 和 _S_oom_realloc()。后两者都有内循环,不断调用 “内存不足处理例程”,期望在某次调用之后,可以获得足够的内存来完成所需求的内存分配,如果 “内存不足处理例程” 并未被客端设定,_S_oom_malloc() 和 _S_oom_realloc() 便会调用 __THROW_BAD_ALLOC,丢出 bad_alloc 异常信息。