我们再来学习内存的配置与释放(定义在头文件 <stl_alloc.h> 中)。其设计思想为:
- 向 system heap 要求空间;
- 考虑多线程 (multi-threads) 状态;
- 考虑内存不足时的应变措施;
- 考虑过多 “小型区块” 可能造成的内存碎片 (fragment) 问题。
我们后面将通过分析源码来了解这设计思想是如何在设计中体现的。SGI 设计了双层级配置器,第一级配置器直接使用 malloc() 和 free(),第二级则视情况采用不同策略,并采用了复杂的内存池(memory pool) 整理方式。
整个设计究竟只开放第一级配置器,或是同时开放第二级配置器,取决于宏 __USE_MALLOC 是否被定义。
1 2 3 4 5 6 7 8 9 10
| # ifdef __USE_MALLOC ...
typedef __malloc_alloc_template<0> malloc_alloc; typedef malloc_alloc alloc; #else ...
typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc; #endif
|
最后都定位于 alloc,因为通常我们使用缺省的空间配置器,而 SGI STL 的每个容器都已经指定其缺省的空间配置器 alloc。
无论 alloc 被定义为第一级或第二级配置器,SGI 还为它再封装一个接口如下,使配置器的接口能够符合STL规格:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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 接口。
本文就着重分析第一级配置器 __malloc_alloc_template
先学习 allocate()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #define __THROW_BAD_ALLOC fprintf(stderr, "out of memory\n"); exit(1) static void* allocate(size_t __n) { void* __result = malloc(__n); if (0 == __result) __result = _S_oom_malloc(__n); return __result; }
#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG template <int __inst> void(*__malloc_alloc_template<__inst>::__malloc_alloc_oom_handler)() = 0; #endif
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; if (0 == __my_malloc_handler) { __THROW_BAD_ALLOC; } (*__my_malloc_handler)(); __result = malloc(__n); if (__result) return(__result); } }
|
上面并非死循环,它有两个退出条件:1.用户没有定义相应的内存不足处理例程,即没有通过释放内存来解决现有内存分配不足的问题,结果抛出异常,直接退出(宏定义);2.在用户定义了释放内存程序例程后,成功分配指定大小内存,返回指向该内存区域的首地址。
再学习 reallocate(),与allocate() 类似,就直接贴源码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| static void* reallocate(void* __p, size_t , 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); } }
|
第一级空间配置器释放内存就更简单了
1 2 3 4
| static void deallocate(void* __p, size_t ) { free(__p); }
|
可以很清楚的看出,第一级配置器以 malloc(),free(),realloc() 等 C 函数执行实际的内存配置、释放、重配置操作,因此,SGI 不能直接使用 C++ 的 set_new_handler(),必须仿真一个类似的 set_malloc_handler()。
1 2 3 4 5 6 7
| 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() 不成功后,改调用 Soom_malloc() 和 Soom_realloc()。后两者都有内循环,不断调用 “内存不足处理例程”,期望在某次调用之后,可以获得足够的内存来完成所需求的内存分配,如果 “内存不足处理例程” 并未被客端设定,Soom_malloc() 和 Soom_realloc() 便会调用 __THROW_BAD_ALLOC,丢出 bad_alloc 异常信息,而后直接利用 exit(1) 中止程序。
所以可以看出,设计 “内存不足处理例程” 是客端的责任,设定 “内存不足处理例程” 也是客端的责任。也就是说 SGI STL 不为你设定。