STL源码剖析(空间配置器)

前言

在STL中,容器的定义中都带一个模板参数,如vector

template <class T, class Alloc = alloc>
class vector {...}

其中第二个参数就是该容器使用的空间配置器,其中缺省使用STL已经实现的空间配置器(alloc),

该配置器使用malloc/free等为vector分配内存。

 

缺省的空间配置器

alloc定义了两级的空间配置器,第一级是对malloc/free简单的封装。

而为了解决内存碎片的问题,跟进行内存管理,alloc实现的第二级的空间配置器。

第二级空间配置器在分配大块内存(大于128bytes)时,会直接调用第一级空间配置器,

而分配小于128bytes的内存时,则使用内存池跟free_list进行内存分配/管理。

 

第一级空间配置器

基本实现如下(跟SGI STL可能有点出入,主要是提取核心的内容)

 1 class base_alloc {
 2 public:
 3     // 只是对malloc/free的简单封装
 4     static void* allocate(size_t n)
 5     {
 6         void* res = malloc(n);
 7         if (0 == res) res = oom_malloc(n);
 8         return res;
 9     }
10     static void* reallocate(void* p, size_t new_sz)
11     {
12         void* res = realloc(p, new_sz);
13         if (0 == res) res = oom_realloc(p, new_sz);
14         return res;
15     }
16     static void deallocate(void* p)
17     {
18         free(p);
19     }
20     // 用来设置内存不足时的处理函数 该函数参数跟返回值都是一个函数指针
21     // 一般会抛出异常/尝试回收内存
22     static void(*set_handler(void(*f)()))()
23     {
24         void(*old)() = _oom_handler;
25         _oom_handler = f;
26         return old;
27     }
28 private:
29     // 用来处理内存不足的情况
30     static void* oom_malloc(size_t n)
31     {
32         void(*my_handler)();
33         void* res;
34 
35         for (;;)
36         {
37             my_handler = _oom_handler;
38             if (0 == my_handler) { return NULL; }
39             (*my_handler)();
40             if (res = malloc(n)) return res;
41         }
42     }
43     // 用来处理内存不足的情况
44     static void* oom_realloc(void* p, size_t n)
45     {
46         void(*my_handler)();
47         void* res;
48 
49         for (;;)
50         {
51             my_handler = _oom_handler;
52             if (0 == my_handler) { return NULL; }
53             (*my_handler)();
54             if (res = reallocate(p, n)) return res;
55         }
56     }
57     // 由用户设置,在内存不足的时候进行处理,由上面两个函数调用
58     static void(*_oom_handler)();
59 };
60 
61 // 处理函数默认为0 
62 void(*base_alloc::_oom_handler)() = 0;
View Code

它可以设定一个处理内存不足的时候的处理函数(跟set_new_handler类似)。

 

第二级空间配置器

该配置器维护一个free_list,这是一个指针数组。

在分配内存的时候,补足8bytes的倍数,free_list数组中每个指针分别管理分配大小为8、16、24、32...bytes的内存。

下图表示从二级空间配置器中分配内存时是如何维护free_list的(建议参考下面源码阅读)。

开始所有指针都为0,没有可分配的区块时(就是free_list[i]==0)会从内存池中分配内存(默认分配20个区块)插入到free_list[i]中。

然后改变free_list[i]的指向,指向下一个区块(free_list_link指向下一个区块,如果没有则为0)。

 

下图表示二级空间配置器回收内存时是如何维护free_list结构的。

回收的时候只是将区块插入到free_list[i]的开头,这块内存用于下次分配的时候使用。

该配置器实现如下(同样提取核心的部分):

  1 enum { _ALIGN = 8 };    // 对齐
  2 enum { _MAX_BYTES = 128 }; // 区块大小上限
  3 enum { _NFREELISTS = _MAX_BYTES / _ALIGN }; // free-list个数
  4 
  5 class default_alloc {
  6 private:
  7     // 将bytes上调到8的倍数
  8     static size_t ROUND_UP(size_t bytes)
  9     {
 10         return (bytes + _ALIGN - 1) & ~(_ALIGN - 1);
 11     }
 12 private:
 13     union obj {
 14         union obj* free_list_link;
 15         char client_data[1];
 16     };
 17 private:
 18     // 16个free-lists 各自管理分别为8,16,24...的小额区块
 19     static obj* free_list[_NFREELISTS];
 20     // 根据区块大小,决定使用第n号free-list
 21     static size_t FREELIST_INDEX(size_t bytes)
 22     {
 23         return (bytes + _ALIGN - 1) / _ALIGN - 1;
 24     }
 25     // 分配内存,返回一个大小为n的区块,可能将大小为n的其他区块加入到free_list
 26     static void* refill(size_t n)
 27     {
 28         // 默认分配20个区块
 29         int nobjs = 20;
 30         char* chunk = chunk_alloc(n, nobjs);
 31         obj** my_free_list;
 32         obj* result, *current_obj, *next_obj;
 33 
 34         // 如果只分配了一个区块,直接返回
 35         if (1 == nobjs) return chunk;
 36         // 否则将其他区块插入到free list
 37         my_free_list = free_list + FREELIST_INDEX(n);
 38         result = (obj*)chunk;
 39         // 第一个区块返回 后面的区块插入到free list
 40         *my_free_list = next_obj = (obj*)(chunk + n);
 41         for (int i = 1;; ++i)
 42         {
 43             current_obj = next_obj;
 44             next_obj = (obj*)((char*)next_obj + n);
 45             // 最后一个next的free_list_link为0
 46             if (nobjs - 1 == i)
 47             {
 48                 current_obj->free_list_link = 0;
 49                 break;
 50             }
 51             current_obj->free_list_link = next_obj;
 52         }
 53         return result;
 54     }
 55     // 分配内存
 56     // 在内存池容量足够时,只调整start_free跟end_free指针
 57     // 在内存池容量不足时,调用malloc分配内存(2 * size * nobjs +  ROUND_UP(heap_size >> 4),每次调整heap_size += 本次分配内存的大小) 
 58     static char* chunk_alloc(size_t size, int& nobjs);
 59 
 60     static char* start_free; //内存池的起始位置
 61     static char* end_free;     //内存池的结束位置
 62     static size_t heap_size;         //分配内存时的附加量
 63 public:
 64     static void* allocate(size_t n)
 65     {
 66         obj** my_free_list;
 67         obj* result;
 68         // 大于128就调用第一级空间配置器
 69         if (n > (size_t)_MAX_BYTES)
 70             return base_alloc::allocate(n);
 71 
 72         // 寻找适当的free-list
 73         my_free_list = free_list + FREELIST_INDEX(n);
 74         result = *my_free_list;
 75         // 没有可用的free list
 76         if (result == 0)
 77             return refill(ROUND_UP(n));
 78 
 79         // 调整free list 移除free list
 80         *my_free_list = result->free_list_link;
 81         return result;
 82     }
 83     static void deallocate(void* p, size_t n)
 84     {
 85         obj* q = (obj*)p;
 86         obj** my_free_list;
 87 
 88         // 大于128就调用第一级空间配置器
 89         if (n > (size_t)_MAX_BYTES)
 90         {
 91             base_alloc::deallocate(p);
 92             return;
 93         }
 94 
 95         // 寻找对应的free list
 96         my_free_list = free_list + FREELIST_INDEX(n);
 97         // 调整free list 回收区块(将区块插入到my_free_list)
 98         q->free_list_link = *my_free_list;
 99         *my_free_list = q;
100     }
101     static void reallocate(void* p, size_t old_sz, size_t new_sz);
102 };
103 
104 char* default_alloc::start_free = 0;
105 char* default_alloc::end_free = 0;
106 size_t default_alloc::heap_size = 0;
107 default_alloc::obj* default_alloc::free_list[_NFREELISTS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
View Code

 

posted @ 2016-10-22 15:28  Runnyu  阅读(713)  评论(0编辑  收藏  举报