一、空间的配置与释放,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 异常状态之前,会先调用由客端指定的处理例程。)
设计“内存不足处理例程”是客端的责任,设定“内存不足处理例程”也是客端的责任。