一般而言,我们习惯C++内存申请操作和释放操作如下:
class Foo{};
Foo * pf = new Foo; //1.申请空间2.调用构造函数
delete pf; //1.析构函数2.释放空间
但是为了精确分工,STL Allocator这两个阶段分开来。PS:以下代码不讨论多线程情况
内存申请:alloc::allocate()负责。
内存释放:alloc::deallocate()负责。
对象构造:全局 ::construct()负责。
对象析构:全局 ::destroy()负责。
1.<stl_construct.h> 对象构造和析构关键代码
【::construct()和::destroy()被设计为全局函数。::destory()有2个版本,第一个版本接受一个指针,调用该析构函数即可,第二个版本考虑到大范围的对象析构可能无关痛痒(trivial destructor),使用value_type()获得迭代器所指对象的型别,再利用__tyep_traits<T>判断trivial?不做:调用第一个版本】
#include <new.h> //为了使用placement new template <class T1, class T2> inline void construct(T1* p, const T2& value) { new (p) T1(value); // placement new; 显示调用构造函数T1(value); } // 以下是destroy() 第一版本,接受一个对象指针。 template <class T> inline void destroy(T* pointer) { pointer->~T(); // 析构 } // 以下是destroy() 第二版本,接受两个迭代器,此函数找出元素得数值类型 // 利用__type_traits<> 采用适当措施 template <class ForwardIterator> inline void destroy(ForwardIterator first, ForwardIterator last) { __destroy(first, last, value_type(first)); } // 判断对象的value type是否有trivial destructor template <class ForwardIterator, class T> inline void __destroy(ForwardIterator first, ForwardIterator last, T*) { typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor; __destroy_aux(first, last, trivial_destructor()); } // 如果对象的型別(value type)有non-trivial(要紧的) destructor //循环调用destroy一个版本 template <class ForwardIterator> inline void __destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) { for ( ; first < last; ++first) destroy(&*first); } // 如果对象的型別(value type)有trivial(无关紧要的) destructor template <class ForwardIterator> inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) { //不处理 } // 以下是destroy() 第二版本針對迭代器為char* 和wchar_t* 的特化版 inline void destroy(char*, char*) {} inline void destroy(wchar_t*, wchar_t*) {}
2.<stl_alloc.h> 空间的申请和释放
【C++内存申请基本操作:::operator new(),内存释放基本操作:::operator delete()。SGI用malloc和free这2个C函数代替完成内存的申请和释放。考虑到内存碎片问题,SGI设计了两级配置器,第一级直接使用malloc和free,包括了内存不足处理机制,需要调用者自己提供。第二级判断配置区超过128字节,调用第一级配置器。小于128字节,采用内存池管理】
2.1 配置器接口
//简单的转调用,可能第一级或者第二级配置器
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)); } };
2.2 vector简单使用例子
template<class T, class Alloc> class vector { protected: typedef simple_alloc<value_type,Alloc> data_allocator; void deallocate() { if(...) data_allocator::deallocate(start,end_of_storage - start); } };
2.3 第一级配置器
【SGI第一级配置器的allocate和realloc都是在调用C函数malloc和realloc不成功后,改调用oom_malloc和oom_realloc,后两者都有内循环,不断调用你指定的内存不足处理函数】
// Malloc-based allocator. Typically slower than default alloc below. // Typically thread-safe and more storage efficient. #ifdef __STL_STATIC_TEMPLATE_MEMBER_BUG # ifdef __DECLARE_GLOBALS_HERE void (* __malloc_alloc_oom_handler)() = 0; // g++ 2.7.2 does not handle static template data members. # else extern void (* __malloc_alloc_oom_handler)(); # endif #endif //inst没排上用场 template <int inst> class __malloc_alloc_template { private: //处理内存不足 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) { //第一级直接用malloc; void *result = malloc(n); //malloc内存不足补救函数 if (0 == result) result = oom_malloc(n); return result; } static void deallocate(void *p, size_t /* n */) { //直接free free(p); } static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz) { //直接使用realloc 下面为补救函数 void * result = realloc(p, new_sz); if (0 == result) result = oom_realloc(p, new_sz); return result; } static void (* set_malloc_handler(void (*f)()))() { //指定你的out-of-memory处理函数 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 template <int inst> //你的out-of-memory处理函数初始为 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); } }
2.4 第二级配置器
【每次先申请一大块内存作为内存池,并且维护16条链表,分别从8-16-24-32……-128字节(配置器会主动将当前内存申请提升到8的倍数),链表负责配置出去(配置出去时链表不再指向)和回收回来(回收回来时链表重新指向),当链表空间不足时向内存池申请。】
2.4.1 链表节点结构
//利用union的性质使得链表既是指针又是实际区块 union obj { union obj * free_list_link; char client_data[1]; //柔性数组 };
2.4.2 配置器类内代码
enum {__ALIGN = 8}; enum {__MAX_BYTES = 128}; enum {__NFREELISTS = __MAX_BYTES/__ALIGN}; #endif template <bool threads, int inst> class __default_alloc_template { private: //扩展到__ALIGN的倍数也即是的倍数 static size_t ROUND_UP(size_t bytes) { return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1)); } private: union obj { union obj * free_list_link; char client_data[1]; /* The client sees this. */ }; private: //16条链表 static obj * free_list[__NFREELISTS]; //根据申请的大小快速找到所属链表 static size_t FREELIST_INDEX(size_t bytes) { return (((bytes) + __ALIGN-1)/__ALIGN - 1); } //向内存池申请n大小的区间n为的倍数 static void *refill(size_t n); //内存池成处理申请函数一般为一次性配置nobjs个size大小的块nobjs默认为 //不足时,nobjs返回不定数目 static char *chunk_alloc(size_t size, int &nobjs); //内存池起始位置只在chunk_alloc变化 static char *start_free; //内存池结束位置只在chunk_alloc变化 static char *end_free; //内存池heap_size static size_t heap_size; public: //申请 static void * allocate(size_t n); //回收 static void deallocate(void *p, size_t n); static void * reallocate(void *p, size_t old_sz, size_t new_sz); } ;
2.4.3 allocate()实现
/* n must be > 0 */ static void * allocate(size_t n) { obj * __VOLATILE * my_free_list; obj * __RESTRICT result; //大于字节就调用第一级配置器 if (n > (size_t) __MAX_BYTES) { return(malloc_alloc::allocate(n)); } //找到链表中的所属链表 my_free_list = free_list + FREELIST_INDEX(n); result = *my_free_list; //链表没有可用的块调用refill申请 if (result == 0) { void *r = refill(ROUND_UP(n)); return r; } //调整链表不再指向这块被使用的块 *my_free_list = result -> free_list_link; return (result); };
2.4.4 deallocate()实现
/* p may not be 0 */ static void deallocate(void *p, size_t n) { obj *q = (obj *)p; obj * __VOLATILE * my_free_list; //大于使用第一级配置器 if (n > (size_t) __MAX_BYTES) { malloc_alloc::deallocate(p, n); return; } //找到所属链表 my_free_list = free_list + FREELIST_INDEX(n); //调整链表回收区块 q -> free_list_link = *my_free_list; //链表重新指向回收块 *my_free_list = q; }
2.4.5 refill()实现
void* __default_alloc_template<threads, inst>::refill(size_t n) { //默认申请块数 int nobjs = 20; //nobjs为传引用 char * chunk = chunk_alloc(n, nobjs); obj * __VOLATILE * my_free_list; obj * result; obj * current_obj, * next_obj; int i; //只有一块返回给调用者链表无新节点 if (1 == nobjs) return(chunk); //不止一块找到所属链表 my_free_list = free_list + FREELIST_INDEX(n); //对新的区块建链表 result = (obj *)chunk; //链表指向从内存池拿出的链表 *my_free_list = next_obj = (obj *)(chunk + n); //将新链表之间的节点串接起来 for (i = 1; ; i++) //从开始第个返回给申请者 { //当前节点 current_obj = next_obj; //指向下一个节点 next_obj = (obj *)((char *)next_obj + n); if (nobjs - 1 == i) { //尾节点置 current_obj -> free_list_link = 0; break; } else { //当前节点指向下一个节点 current_obj -> free_list_link = next_obj; } } return(result); //返回给申请者 }
2.4.6 内存池chunk_alloc()实现
_default_alloc_template<threads, inst>::chunk_alloc(size_t size, int& nobjs) { char * result; //申请总量 size_t total_bytes = size * nobjs; //内存池余额 size_t bytes_left = end_free - start_free; //余额满足申请总量 if (bytes_left >= total_bytes) { //返回result result = start_free; //内存池可用起始地址+=申请总量 start_free += total_bytes; return(result); } //不足以满足申请总量但是可以供应一个以上区块 else if (bytes_left >= size) { //重新得到能够供应个数 nobjs = bytes_left/size; //重新计算总量 total_bytes = size * nobjs; //同上 result = start_free; start_free += total_bytes; return(result); } //一个区块都提供不了 else { size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4); //余额大于把这些零头凑到链表里去 if (bytes_left > 0) { obj * __VOLATILE * my_free_list = free_list + FREELIST_INDEX(bytes_left); ((obj *)start_free) -> free_list_link = *my_free_list; *my_free_list = (obj *)start_free; } //申请heap空间,补充内存池 start_free = (char *)malloc(bytes_to_get); if (0 == start_free) { int i; obj * __VOLATILE * my_free_list, *p; // Try to make do with what we have. That can't // hurt. We do not try smaller requests, since that tends // to result in disaster on multi-process machines. //以下搜索适当的链表 //适当是指"尚有未用区块的链表" for (i = size; i <= __MAX_BYTES; i += __ALIGN) { //拿到这个链表 my_free_list = free_list + FREELIST_INDEX(i); p = *my_free_list; // if (0 != p) { //调整链表释放未使用区块 //链表起始指向下一个区块 *my_free_list = p -> free_list_link; //内存池开头指向这个区块 start_free = (char *)p; //结尾指向区块结尾 end_free = start_free + i; //再一次调用自身 return(chunk_alloc(size, nobjs)); // Any leftover piece will eventually make it to the // right free list. } } //没办法没内存用了 end_free = 0; // In case of exception. //调用第一级配置器看看out-of-memory机制能否出点力 start_free = (char *)malloc_alloc::allocate(bytes_to_get); // This should either throw an exception // or remedy the situation. Thus we assume it // succeeded. } //heap_size+= heap_size += bytes_to_get; //结束位置重新界定 end_free = start_free + bytes_to_get; return(chunk_alloc(size, nobjs)); } }