注:博客内容均来自于对《STL源码剖析》侯捷,华中科技大学出版社一书的笔记。转载请注明出处。
所有例程在Dev-C++上编译运行,编译选择的是GUN C++11。
// 开头的这段声明足以看出,SGI做的这个版本仅仅是为了在必须考虑向下兼容 // 的时候才会使用,它的其他文件中根本不会用到这个! // DO NOT USE THIS FILE unless you have an old container implementation // that requires an allocator with the HP-style interface. SGI STL // uses a different allocator interface. SGI-style allocators are not // parametrized with respect to the object type; they traffic in void * // pointers. This file is not included by any other SGI STL header. #ifndef DEFALLOC_H #define DEFALLOC_H #include <new.h> #include <stddef.h> #include <stdlib.h> #include <limits.h> #include <iostream.h> #include <algobase.h> // template <class T> inline T* allocate(ptrdiff_t size, T*) { set_new_handler(0); //和我们自己设计的空间配置器基本都是一致的 T* tmp = (T*)(::operator new((size_t)(size * sizeof(T)))); if (tmp == 0) { cerr << "out of memory" << endl; exit(1); } return tmp; } template <class T> inline void deallocate(T* buffer) { ::operator delete(buffer); } template <class T> class allocator { public: typedef T value_type; typedef T* pointer; typedef const T* const_pointer; typedef T& reference; typedef const T& const_reference; typedef size_t size_type; typedef ptrdiff_t difference_type; pointer allocate(size_type n) { return ::allocate((difference_type)n, (pointer)0); } void deallocate(pointer p) { ::deallocate(p); } pointer address(reference x) { return (pointer)&x; } const_pointer const_address(const_reference x) { return (const_pointer)&x; } //这个成员函数之前我们没有设计过 size_type init_page_size() { return max(size_type(1), size_type(4096/sizeof(T))); } size_type max_size() const { return max(size_type(1), size_type(UINT_MAX/sizeof(T))); } }; class allocator<void> { public: typedef void* pointer; }; #endif
通常,C++的内存配置操作和释放操作是这样的:
class Foo{};
Foo *pf = new Foo; //配置内存,然后构造对象
delete pf; //析构对象,然后释放内存
上述过程可以描述成:
1、配置内存(new)
2、构造对象Foo::Foo()
3、析构对象Foo::~Foo()
4、释放内存(delete)
在SGI自己的内存配置器中将这个四个过程分开
1、配置内存(new)----- alloc::allocate()
2、构造对象Foo::Foo() ----- ::construct()
3、析构对象Foo::~Foo() ----- ::destroy()
4、释放内存(delete)----- ::deallocate()
考虑到小区块可能造成的内存破碎问题,SGI设计了双层的配置器*。下图所示:
第一级配置器直接使用malloc()和free();
第二级配置器则复杂的多,它会视不同的情况采取不同的策略(这是提升效率的关键)
采取的策略是:
1、当配置的区块超过128bytes时,视为“足够大”,就调用第一级配置器;
2、当配置的区块小于128bytes的时候,就认为比较小,为了降低额外负担就采用复杂的memory pool整理的方式,便不再求助于第一级配置器。
//没有template型别参数 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); //分配失败则调用out of memory handling 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) { // 第一级配置器直接使用 realloc() void * result = realloc(p, new_sz); //分配失败则调用out of memory handling if (0 == result) result = oom_realloc(p, new_sz); return result; } //类似 set_new_handler() //因为使用的是C版本的malloc realloc所以不能直接使用C++的set_new_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 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); } } typedef __malloc_alloc_template<0> malloc_alloc;
第二级配置器相比第一级配置器复杂的多。它多了一些机制,避免太多小额区造成内存碎片。
SGI的做法:
--如果区块足够大(超过128bytes)移交给第一级配置器处理;
--区块小于128bytes,以内存池的方式进行管理。
上述过程也叫做级次配置:每次配置一大块内存,并维护对应该内存的自由链表。为了方便管理SGI的空间配置器会主动将任何内存需求量上调到8的倍数,并维护16个自由链表,他们各自管理的大小分别是:8,16,24,…, 128 bytes 的小额区块。
自由链表的结构如下:
union obj
{
union obj * free_list_link;
char client_data[1];
};
这样的设计有一个很精妙的地方就是不用为了维护链表而去造成内存的浪费(通常链表中保留一个指针来维护该链表)。但是这里设计成联合体的形式,当该区块的内存还没有被使用的时候,obj就指向下一个区块,当该区块使用的时候,就对client_data赋值,这个赋值的操作会造成obj内容的改变,也就是说这个区块就从该链表中被移除来了。
enum {__ALIGN = 8}; // 小型区块的上调边界 enum {__MAX_BYTES = 128}; // 小型区块的上限 enum {__NFREELISTS = __MAX_BYTES/__ALIGN}; // free-lists 个数 #endif //没有 template <bool threads, int inst> class __default_alloc_template { private: //上调至8的倍数 static size_t ROUND_UP(size_t bytes) { //tips:数字n上调至x的倍数可以使用(n + x - 1) & ~(x - 1) //先将数字上调,然后将低位变成0就好了(二进制情况) return (((bytes) + __ALIGN - 1) & ~(__ALIGN - 1)); } private: union obj //free-lists结点构造 { union obj * free_list_link; char client_data[1]; /* The client sees this. */ }; private: //__NFREELISTS个free-lists结点 static obj * __VOLATILE free_list[__NFREELISTS]; //传入区块大小,决定使用第n号free-list, n从0号开始算起 static size_t FREELIST_INDEX(size_t bytes) { return (((bytes) + __ALIGN - 1) / __ALIGN - 1); } //返回一个大小为n的对象,并可能加入大小为n的其他区块到free-list static void *refill(size_t n); //配置一大块空间,可容纳nobjs个,大小为size的区块 static char *chunk_alloc(size_t size, int &nobjs); static char *start_free; //内存池起始位置 static char *end_free; //内存池结束位置 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); }; //以下是static data member的定义和初值设定 template <bool threads, int inst> char* __default_alloc_template<threads, inst>::chunk_alloc(size_t size, int& nobjs); template <bool threads, int inst> void* __default_alloc_template<threads, inst>::refill(size_t n); template <bool threads, int inst> void* __default_alloc_template<threads, inst>::reallocate(void *p, size_t old_sz, size_t new_sz) template <bool threads, int inst> __default_alloc_template<threads, inst>::obj * __VOLATILE __default_alloc_template<threads, inst> ::free_list[__NFREELISTS] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
static void * allocate(size_t n) { obj * __VOLATILE * my_free_list; obj * __RESTRICT result; //如果n>128就直接调用第一级空间配置器 if (n > (size_t) __MAX_BYTES) { return(malloc_alloc::allocate(n)); } //寻找到16个自由结点中的一个 my_free_list = free_list + FREELIST_INDEX(n); result = *my_free_list; if (result == 0) { //如果没有找到这样的free-list,就准备重新填充free-list void *r = refill(ROUND_UP(n)); return r; } //如果有合适的的free-list,调整free-list然后返回结果 *my_free_list = result -> free_list_link; return (result); };
static void deallocate(void *p, size_t n) { obj *q = (obj *)p; obj * __VOLATILE * my_free_list; //这种定义的含义和作用?后面详述 //大于128就调用第一级配置器 if (n > (size_t) __MAX_BYTES) { malloc_alloc::deallocate(p, n); return; } //寻找对应的free-list my_free_list = free_list + FREELIST_INDEX(n); //调整free-list,回收区块 q -> free_list_link = *my_free_list; *my_free_list = q; }
//返回一个大小为n的对象, 并且有时候会为适当的free list增加结点 //假设 n 已经适当上调到8的倍数 template <bool threads, int inst> void* __default_alloc_template<threads, inst>::refill(size_t n) { int nobjs = 20; //调用chunk_alloc(),尝试取得nobjs个区块作为free list的新节点 //参数nobjs是pass by reference char * chunk = chunk_alloc(n, nobjs); obj * __VOLATILE * my_free_list; obj * result; obj * current_obj, * next_obj; int i; //如果只获得一个区块,这个区块就分配给调用者用,free-list无新节点 if (1 == nobjs) return(chunk); //否则准备调整free list,纳入新的结点 my_free_list = free_list + FREELIST_INDEX(n); //在以下chunk空间内建立free list result = (obj *)chunk; //以下导引free list 指向新配置的空间(取得内存池) *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); }