stl中的空间配置器
一般我们习惯的c++内存配置如下
class Foo { ... }; Foo* pf = new Foo; delete pf;
这里的new实际上分为两部分执行。首先是先用::operator new配置内存,然后执行Foo::Foo()构造对象内容。delete也一样,先运行Foo::~Foo()析构对象,再用::operator delete释放内存。在SGI STL中,这两部分分别在<stl_alloc.h>和<stl_construct.h>中。本文讲的便是<stl_alloc.h>中的故事。
SGI STL中将配置器分为两级。第一级直接用malloc和free管理内存,第二级则使用内存池以避免内存碎片。这两级都由simple_alloc包装起来以符合stl标准。如图
第一级由于没有用operator new,所以要自己实现new-handler机制。我仿写的代码如下
1 #ifndef _MALLOC_ALLOC_H_ 2 #define _MALLOC_ALLOC_H_ 3 4 //定义内存不足又没有定义相关处理函数时抛出的异常 5 #ifndef THROW_OOM 6 # include <stdio.h> 7 # include <stdlib.h> 8 # define THROW_OOM fprintf(stderr, "out of memory\n"); exit(1) 9 #endif 10 11 #include<stdlib.h> 12 13 namespace Chenstl{ 14 15 //第一级空间配置器,直接用mallloc分配内存 16 //当需要分配的空间大于MAX_BYTES时使用 17 class malloc_alloc{ 18 private: 19 static void *oom_malloc(size_t); //声明时可以只写类型啊。。现在才知道 20 static void *oom_realloc(void *,size_t); 21 static void (* malloc_oom_handler)(); //处理malloc时内存不足时的函数指针 22 public: 23 static void *allocate(size_t n); 24 static void decllocate(void *p); 25 26 static void *realloc(void *p, size_t new_sz); 27 //当内存不足时,需要客户端设置handler 28 static void set_malloc_oom_handler(void(*f)()); 29 }; 30 } 31 32 #endif
1 #include "malloc_alloc.h" 2 3 using namespace Chenstl; 4 void *malloc_alloc::allocate(size_t n) 5 { 6 void *result = malloc(n); 7 if (0 == result) result = oom_malloc(n); 8 return result; 9 } 10 11 void malloc_alloc::decllocate(void *p) 12 { 13 free(p); 14 } 15 16 void * malloc_alloc::realloc(void *p, size_t new_sz) 17 { 18 void *result = realloc(p, new_sz); 19 if (0 == result) result = oom_realloc(p, new_sz); 20 return result; 21 } 22 23 //当内存不足时,需要客户端设置handler 24 void malloc_alloc::set_malloc_oom_handler(void(*f)()) 25 { 26 malloc_oom_handler = f; 27 } 28 29 void(*malloc_alloc::malloc_oom_handler)() = 0; 30 31 void *malloc_alloc::oom_malloc(size_t n) 32 {//不断试图获得内存 33 void *result; 34 for (;;) //据说这样比while(1)效果更优 35 { 36 if (0 == malloc_oom_handler) THROW_OOM; 37 (*malloc_oom_handler)(); 38 result = malloc(n); 39 if (result) return result; 40 } 41 } 42 43 void *malloc_alloc::oom_realloc(void *p, size_t n) 44 { 45 void *result; 46 for (;;) 47 { 48 if (0 == malloc_oom_handler) THROW_OOM; 49 (*malloc_oom_handler)(); 50 result = realloc(p, n); 51 if (result) return result; 52 } 53 }
如果需要的区块超过128bytes则用第一级,否则用第二级的内存池管理。为了便于管理,配置器会自动将内存需求量上调到8的倍数(要求20bytes时,自动调整为24bytes)。用16个freelist管理内存池,为节省空间,使用union
union obj { //free-lists的节点构造
union obj *next;
char client[1]; //使用者可见
};
获取内存时的代码及步骤如下
void *default_alloc::allocate(size_t n) { obj *result = 0; obj **my_free_list = 0; if (n > MAX_BYTES) return malloc_alloc::allocate(n); //寻找free lists中合适的一个 my_free_list = free_list + FREELIST_INDEX(n); result = *my_free_list; if(0 == result) {//没有找到可用的freelist,从内存池里取出空间 return refill(ROUND_UP(n)); } //调整freelist *my_free_list = result->next; return result; }
当free list中没有可用区块时,调用refill()为free list填充空间,新的空间取自内存池(由chunk_alloc()完成)。如果内存池不够,则malloc之,如果系统heap空间也不够,chunk_alloc()就寻找还有空闲区块的free list并将其内存充公,如果还是不够就调用第一级配置器。第一级配置器有实现new-handler机制,内存不够会抛出异常。
#ifndef _DEFAULT_ALLOC_H #define _DEFAULT_ALLOC_H namespace Chenstl{ //使用内存池以减少碎片 class default_alloc { private: enum { ALIGN = 8}; enum { MAX_BYTES = 128 }; enum { NFREELISTS = 16 }; //static const int ALIGN = 8; //static const int MAX_BYTES = 128; //static const int NFREELISTS = 16; //MAX_BYTES/ALIGN union obj { //free-lists的节点构造 union obj *next; char client[1]; }; //freelist static obj *free_list[NFREELISTS]; static char *start_free; //内存池的起始位置 static char *end_free; //内存池的终止位置 static size_t heap_size; private: //将bytes上调至8的倍数 static size_t ROUND_UP(size_t bytes) { return ((bytes +ALIGN - 1) & ~(ALIGN - 1)); } //获取合适的区块在freelist中的位置 static size_t FREELIST_INDEX(size_t __bytes) { return (((__bytes)+(size_t)ALIGN - 1) / (size_t)ALIGN - 1); } //返回一个大小为n的对象,并可能加入大小为n的其他区块到free-list static void *refill(size_t n); //配置一大块空间,可容纳nobjs个大小为size的区块 //如果配置nobjs个区块有所不便,nobjs可能会降低 static char *chunk_alloc(size_t size, int &nobjs); public: static void *allocate(size_t n); static void deallocate(void *p, size_t n); static void *realloc(void *p, size_t old_sz, size_t new_sz); }; } #endif
#include "default_alloc.h" #include "malloc_alloc.h" using namespace Chenstl; default_alloc::obj *default_alloc::free_list[NFREELISTS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; char *default_alloc::start_free = 0; //内存池的起始位置 char *default_alloc::end_free = 0; //内存池的终止位置 size_t default_alloc::heap_size = 0; void *default_alloc::allocate(size_t n) { obj *result = 0; obj **my_free_list = 0; if (n > MAX_BYTES) return malloc_alloc::allocate(n); //寻找free lists中合适的一个 my_free_list = free_list + FREELIST_INDEX(n); result = *my_free_list; if(0 == result) {//没有找到可用的freelist,从内存池里取出空间 return refill(ROUND_UP(n)); } //调整freelist *my_free_list = result->next; return result; } void default_alloc::deallocate(void *p, size_t n) { } //返回一个大小为n的对象,并可能加入大小为n的其他区块到freelist //在ANSI c中,void *不允许进行加减操作,所以chunk用char * void *default_alloc::refill(size_t n) { int objs = 20; char *chunk = chunk_alloc(n, objs); obj *next, *current; obj *result; obj **my_free_list; if (1 == objs) //只取出一个区块 return chunk; my_free_list = free_list + FREELIST_INDEX(n); result = (obj *)chunk; //这一块返回给客户端 //将freellist指向分配的区域 *my_free_list = next = (obj *)chunk + n; for (int i = 1;; i++) { current = next; next = (obj *)((char *)next + n); //这里注意不能直接用next+n if (i == objs - 1) { current->next = 0; break; } else current->next = next; } return result; } char *default_alloc::chunk_alloc(size_t size, int &nobjs) { char *result = 0; size_t total_bytes = size*nobjs; size_t bytes_left = end_free - start_free; //内存池剩余空间 if (bytes_left >= total_bytes) {//内存池足够提供所需内存 result = start_free; start_free += total_bytes; return result; } else if (bytes_left >= size) {//内存池足够供应一个以上的区块 nobjs = bytes_left / size; total_bytes = nobjs * size; 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) {//将内存池的零头分配给合适的freelist obj **my_free_list = free_list + FREELIST_INDEX(bytes_left); ((obj *)start_free)->next = *my_free_list; *my_free_list = (obj *)start_free; } start_free = (char *)malloc(bytes_to_get); if (!start_free) {//系统堆内存不足,寻找还未使用的freelist obj *p = 0; obj **my_free_list = 0; for (int i = size; i < MAX_BYTES; ++i) { my_free_list = free_list + FREELIST_INDEX(i); p = *my_free_list; if (0 != p) {//还有未使用的freelist start_free = (char *)p; *my_free_list = p->next; end_free = start_free + i; //递归调用,修正nobjs return chunk_alloc(size, nobjs); } } //没内存可用,寄希望于第一级的new-handler或抛出异常 end_free = 0; start_free = (char *)malloc_alloc::allocate(bytes_to_get); } heap_size += bytes_to_get; end_free = start_free + bytes_to_get; return chunk_alloc(size, nobjs);//递归调用,修正nobjs } }