首页 > 代码库 > 自己动手实现STL 01:内存配置器的实现(stl_alloc.h)

自己动手实现STL 01:内存配置器的实现(stl_alloc.h)

一、前言

  在STL中,容器是其中的重中之重,基本的STL中的算法,仿函数等都是围绕着容器实现的功能。而,内存配置器,是容器的实现的基础。所以,我第一次要去编写便是内存配置器的实现。在STL中,内存配置器的实现是在stl_alloc.h中。

二、配置器原理简要介绍

  在SGI STL中配置分为两级,第一级配置器和第二级配置器。两者关系如下:

图1:第一级配置器和第二级配置器

  在SGI STL中内存的配置器分为两级,第一级配置器和第二级配置器。第一级配置器就是,直接调用系统的malloc分配内存。对于小于128byte的内存分配请求,我们使用第二级内存配置器。第二级内存配置器是一个内存池,其中共有16个已分配好的区块形成的链表。这16个链表的中区块的大小依次是8,16,24....128byte,都8的倍数。每次请求小于等于128byte时,把请求的大小上调到最接近的8的倍数,比如,7就上调为8,30就上调为32,然后找到对应大小区块的链表,从中取下一个区块返回给请求。

  第二级配置器使用内存池的好处就是,可以避免太多小额区块造成的内存破碎。同时,每次分配内存都需要调用malloc去分配,malloc调用的消耗的时间等资源是一定的,对于大区块的分配这样的消耗的时间等资源,是没有什么的。但是对于小额区块的,它的消耗就显得太不值的了。我们可以采用一次预分配一块连续的大区块,把它串成一个定额大小的区块链表,(8的倍数字节),下次使用的时候,从对应预分配的区块链表中找一个能够满足大小的,最接近的区块直接返回给请求,这样就可以避免对于小区块的malloc调用。同时对于小区块的释放,可以直接把它加入到内存池中对应大小的链表中即可。

图2:把连续大区块串成小额区块

  在第二级配置器中维持一个free_list[16]数组,其中存储着8-128byte各种大小区块的链表的头节点地址。

图3:free_list[16]结构

  每次分配只要从适当大小的链表中取出第一个节点,返回给请求,让free_list对应的位置的保存链表的下一个节点地址。释放的时候,对于小于等于128byte的区块,只要把它插入对应大小区块链表的头,然后调整保存的链表头结点的地址就可以了。当需求大小区块的使用完了的时候,可以利用malloc一次分配适当大小的大区块,然后把它分割为对应大小的小区块,在把它们串联起来形成链表,把第一个节点地址存入对应的free_list的位置中。

三、实现源代码

  上面只是配置器的简介,在源码中有详细的注释。源码如下:

  1 /*************************************************************************  2     > File Name: stl_alloc_wjzh.h  3     > Author: wjzh  4     > Mail: wangjzh_1@163.com   5     > Created Time: 2014年10月31日 星期五 16时06分23秒  6  ************************************************************************/  7   8 #ifndef __SGI_STL_INTERNAL_ALLOC_WJZH_H  9 #define __SGI_STL_INTERNAL_ALLOC_WJZH_H 10  11 #ifdef __SUNPRO_CC 12 #    define __PRIVATE public 13 #else 14 #    define __PRIVATE private 15 #endif 16  17 #ifdef __STL_STATIC_TEMPLATE_MEMBER_BUG 18 #    define __USE_MALLOC 19 #endif 20  21 #if 0 22 #    include <new> 23 #    define __THROW_BAD_ALLOC throw bad_alloc 24 #elif !defined(__THROW_BAD_ALLOC) 25 //#    include <iostream.h> 26 #include <iostream> 27 #    define __THROW_BAD_ALLOC std::cerr << "out of memory" << std::endl; exit(1) 28 #endif 29  30 #ifndef __ALLOC 31 #    define __ALLOC alloc 32 #endif 33 #ifdef __STL_WIN32THREADS 34 #    include <windows.h> 35 #endif 36  37 #include <stddef.h> 38 #include <stdlib.h> 39 #include <string.h> 40 #include <assert.h> 41 #ifndef __RESTRICT 42 #    define __RESTRICT 43 #endif 44  45 #if !defined(__STL_PTHREADS) && !defined(_NOTHREADS)  46  && !defined(__STL_SGI_THREADS) && !defined(__STL_WIN32THREADS) 47 #    define _NOTHREADS 48 #endif 49  50 #ifdef __STL_PTHREADS 51 #    include <pthread.h> 52 #    define __NODE_ALLOCATOR_LOCK  53         if (threads) pthread_mutex_lock(&__node_allocator_lock) 54 #    define __NODE_ALLOCATOR_UNLOCK  55         if (threads) pthread_mutex_unlock(&__node_allocator_lock) 56 #    define __NODE_ALLOCATOR_THREADS true 57 #    define __VOLATILE volatile 58 # endif 59 # ifdef __STL_WIN32THREADS 60 #    define __NODE_ALLOCATOR_LOCK  61         EnterCriticalSection(&__node_allocator_lock) 62 #    define __NODE_ALLOCATOR_UNLOCK  63         LeaveCriticalSection(&__node_allocator_lock) 64 #    define __NODE_ALLOCATOR_THREADS true 65 #    define __VOLATILE volatile 66 # endif 67  68 # ifdef __STL_SGI_THREADS 69     extern "C" { 70         extern int __us_rsthread_malloc; 71     } 72 #    define __NODE_ALLOCATOR_LOCK if (threads && __us_rsthread_malloc)  73 { __lock(&__node_allocator_lock);    } 74 #    define __NODE_ALLOCATOR_UNLOCK if(threads && __us_rsthread_malloc)  75 { __unlcok(&__node_allocator_lock);    } 76 #    define __NODE_ALLOCATOR_THREADS true 77 #    define __VOLATILE volatile 78 # endif 79  80 # ifdef _NOTHREADS 81 #    define __NODE_ALLOCATOR_LOCK 82 #    define __NODE_ALLOCATOR_UNLOCK 83 #    define __NODE_ALLOCATOR_THREADS false 84 #    define __VOLATILE 85 # endif 86  87 __STL_BEGIN_NAMESPACE 88  89 #if defined(__sgi) && !defined(__GNUC__) && (_MIPS_SIM != _MIPS_SIM_ABI32) 90 #pragma set woff 1174 91 #endif 92  93 #ifdef __STL_STATIC_TEMPLATE_MEMBER_BUG 94 #    ifdef __DECLARE_GLOBALS_HERE 95         void (* __malloc_alloc_oom_handler)() = 0; 96 #else 97         extern void (* __malloc_alloc_oom_handler)(); 98 # endif 99 #endif100 101 template <int inst>102 class __malloc_alloc_template {103     private:104         static void *oom_malloc(size_t);105         static void *oom_realloc(void *, size_t);106 #ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG107         static void (* __malloc_alloc_oom_handler)();108 #endif109     public:110         static void* allocate(size_t n)111         {112             void *result = malloc(n);113             if (0 == result) result = oom_malloc(n);114             return result;115         }116 117         static void deallocate(void *p, size_t)118         {119             free(p);120         }121 122         static void * reallocate(void *p, size_t , size_t new_sz)123         {124             void *result = realloc(p, new_sz);125             if (0 == result) result = oom_realloc(p, new_sz);126             return result;127         }128 129         static void (* set_malloc_handler(void (*f)()))()130         {131             void (* old)() = __malloc_alloc_oom_handler;132             __malloc_alloc_oom_handler = f;133             return(old);134         }135 136 };137 138 // malloc_alloc out-of-memory handling139 // 分配内存时,没有内存时的处理140 141 #ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG142 template <int inst>143 void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0;144 #endif145 146 template <int inst>147 void * __malloc_alloc_template<inst>::oom_malloc(size_t n)148 {149     void (* my_malloc_handler)();150     void *result;151     for (;;)152     {153         my_malloc_handler = __malloc_alloc_oom_handler;154         if (0 == my_malloc_handler) { __THROW_BAD_ALLOC; }155         (*my_malloc_handler)();156         result = malloc(n);157         if (result) return (result);158     }159 }160 161 template <int inst>162 void * __malloc_alloc_template<inst>::oom_realloc(void *p, size_t n)163 {164     void (* my_malloc_handler)();165     void *result;166 167     for (;;)168     {169         my_malloc_handler = __malloc_alloc_oom_handler;170         if (0 == my_malloc_handler) { __THROW_BAD_ALLOC; }171         (*my_malloc_handler)();172         result = realloc(p, n);173         if (result) return (result);174     }175 }176 177 typedef __malloc_alloc_template<0> malloc_alloc;178 179 template<class T, class Alloc>180 class simple_alloc181 {182     public:183         static T *allocate(size_t n)184         {185             return 0 == n ? 0 : (T*) Alloc::allocate(n * sizeof(T));186         }187         static T *allocate(void)188         {189             return (T*) Alloc::allocate(sizeof(T));190         }191         static void deallocate(T *p, size_t n)192         {193             if (0 != n) Alloc::deallocate(p, n * sizeof(T));194         }195         static void deallocate(T *p)196         {197             Alloc::deallocate(p, sizeof(T));198         }199 };200 201 // Allocator adaptor to check size arguments for debugging.202 template <class Alloc>203 class debug_alloc204 {205     private:206         enum { extra = 8};    // Size of space used to store size. Note207                     // that this must be large enough to preserve208                     // alignment.209 210     public:211         static void * allocate(size_t n)212         {213             char *result = (char *)Alloc::allocate(n + extra);214             *(size_t *)result = n;    //前size_t大小用来记录result的大小,实际预分配了extra个字节,用来存储大小,215                         //但是只用size_t字节,因为不同系统size_t大小不同,8个字节足够满足所有系统了216             return result + extra;217         }218         219         static void deallocate(void *p, size_t n)220         {221             char * real_p = (char *)p - extra;222             assert(*(size_t *)real_p == n);223             Alloc::deallocate(real_p, n + extra);224         }225 226         static void * reallocate(void *p, size_t old_sz, size_t new_sz)227         {228             char * real_p = (char *)p - extra;229             assert(*(size_t *)real_p == old_sz);230             char * result = (char *)231                             Alloc::reallocate(real_p, old_sz + extra, new_sz+ extra);232             *(size_t *)result = new_sz;233             return result + extra;234         }235 236 };237 238 #ifdef __USE_MALLOC239 240 typedef malloc_alloc alloc;241 typedef malloc_alloc single_client_alloc;242 243 #else244 245 //下面是第二级配置器246 //主要是维护一个内存池,用来小于128byte的小型区块内存的分配247 //其中,有多个链表,各链表中的node大小从8-128byte,都是8的倍数248 //分配时,不是8的倍数,上调至最近的8的倍数,249 //然后从相应链表中取下一个对应大小的node分配给请求250 #ifdef __SUNPRO_CC251     enum {__ALIGN = 8};  //小型区块的上调边界252     enum {__MAX_BYTES = 128};   //小型区块的上限253     enum {__NFREELISTS = __MAX_BYTES/__ALIGN};254 #endif255 256 //第二级配置器257 template <bool threads, int inst>258 class __default_alloc_template259 {260     private:261 # ifndef __SUNPRO_CC262     enum {__ALIGN = 8};  //小型区块的上调边界263     enum {__MAX_BYTES = 128};   //小型区块的上限264     enum {__NFREELISTS = __MAX_BYTES/__ALIGN};265 # endif266     //大小上调至8的倍数267     static size_t ROUND_UP(size_t bytes)268     {269         return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));270     }271 __PRIVATE:272     union obj273     {274         union obj * free_list_link;  //用于在链表中指向下一个节点275         char client_data[1]; //用于存储实际区块的内存地址,由于这是一个union,很好的节约了这个数据的内存276     };277     private:278 # ifdef __SUNPRO_CC279     static obj * __VOLATILE free_list[];280 # else281     static obj * __VOLATILE free_list[__NFREELISTS];282 # endif283     static size_t FREELIST_INDEX(size_t bytes)284     {285         return (((bytes) + __ALIGN-1)/__ALIGN - 1);286     }287 288     //返回大小为n的对象,并可能加入大小为n的其他区块到free list289     static void *refill(size_t n);290     //配置一块空间,可容纳nobjs个大小为"size"的区块291     //如果配置nobjs个区块有所不便,nobjs可能会降低292     static char *chunk_alloc(size_t size, int &nobjs);293 294     //chunk 分配、配置的状态295     static char *start_free; //内存池起始位置。只在chunk_alloc()中变化296     static char *end_free;   //内存池结束位置。只在chunk_alloc()中变化297     static size_t heap_size;298 /*299 # ifdef __STL_SGI_THREADS300     static volatile unsigned long __node_allocator_lock;301     static void __lock(volatile unsigned long *);302     static inline void __unlock(volatile unsigned long *);303 # endif304 */305 306 # ifdef __STL_PTHREADS307     static pthread_mutex_t __node_allocator_lock;308 # endif309 310 # ifdef __STL_WIN32THREADS311     static CRITICAL_SECTION __node_allocator_lock;312     static bool __node_allocator_lock_initialized;313 314     public:315         __default_alloc_template()316         {317             //假定第一个构造函数的调用在线程启动起318             if (!__node_allocator_lock_initialized)319             {320                 InitializedCriticalSection(&__node_allocator_lock);321                 __node_allocator_lock_initialized = true;322             }323         }324     private:325 # endif326 327         class lock328         {329             public:330                 lock() { __NODE_ALLOCATOR_LOCK; }331                 ~lock() { __NODE_ALLOCATOR_UNLOCK; }332         };333         friend class lock;334     public:335         //n必须大于0336         static void * allocate(size_t n)337         {338             obj * __VOLATILE * my_free_list;339             obj * __RESTRICT result;340 341             //需要分配的大小大于二级配置器的__MAX_BYTES,直接使用第一级配置器342             if (n > (size_t) __MAX_BYTES)343             {344                 return(malloc_alloc::allocate(n));345             }346             my_free_list = free_list + FREELIST_INDEX(n); //找到比需要分配的大小大,且最接近的大小块所在的链表所在free_list数组中的位置347             348             //如果支持线程,定义lock349 #        ifndef _NOTHREADS350             lock lock_instance;351 #        endif352             result = *my_free_list;  //取出找的对应链表的指向第一个节点的指针353             if (result == 0)    //对应的链表中没有剩余未分配的节点区块354             {355                 void *r = refill(ROUND_UP(n));    //再从内存池中分配一批,需求大小的区块(实际大小是请求大小上调至8的倍数后的数值),356                                 //然后,放入对应链表,待分配给请求357                 return r;358             }359             //如果对应大小区块的链表中不为空,还有待分配的区块,取出第一个节点360             *my_free_list = result -> free_list_link;361             return (result);362         };363 364         //p不可以是0365         static void deallocate(void *p, size_t n)366         {367             obj *q = (obj *)p;368             obj * __VOLATILE * my_free_list;369 370             //大于区块大小上限的,直接调用第一级配置器释放371             if (n > (size_t) __MAX_BYTES)372             {373                 malloc_alloc::deallocate(p, n);374                 return;375             }376             my_free_list = free_list + FREELIST_INDEX(n);377             //需要修改my_free_list,如果支持线程,那么需要加上互斥锁378 #            ifndef _NOTHREADS379             lock lock_instance;380 #            endif381 382             //头插法,插入对应大小的区块链表383             q -> free_list_link = *my_free_list;384             *my_free_list = q;385             //lock是静态对象,到此,将自动析构销毁,在其析构函数中,会释放锁386         }387 388         static void *reallocate(void *p, size_t old_sz, size_t new_sz);389 390 };391 392 typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc;393 typedef __default_alloc_template<false, 0> single_client_alloc;394 395 396 // 我们从大的chunks中分配内存,是为了避免使用malloc太频繁了397 // 假设size已经适当上调至8的倍数398 // 我持有allocation lock399 // 注意参数objs 是pass by reference400 template <bool threads, int inst>401 char *402 __default_alloc_template<threads, inst>::chunk_alloc(size_t size, int& nobjs)403 {404     char * result;405     size_t total_bytes = size * nobjs;406     size_t bytes_left = end_free - start_free;    //内存池剩余空间407 408     if (bytes_left >= total_bytes)409     {410         //内存池中剩余的空间足够满足需求量411         result = start_free;412         start_free += total_bytes;413         return(result);414     }415     else if (bytes_left >= size)416     {417         //内存池剩余空间不能完全满足需求量,但足够供应一个及以上的区块418         nobjs = bytes_left/size;419         total_bytes = size * nobjs;420         result = start_free;421         start_free += total_bytes;422         return (result);423     }424     else425     {426         //内存池连一个区块的大小都无法满足427         size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);428         //以下试着让内存池中的残余零头还有利用价值429         if (bytes_left > 0)430         {431             //内存池中内还有一些零头,先配给适当的free list432             //首先寻找适当的free list433             obj * __VOLATILE * my_free_list = 434                     free_list + FREELIST_INDEX(bytes_left);435 436             //调整free list,将内存池中残余的空间编入437             ((obj *)start_free) -> free_list_link = *my_free_list; 438             *my_free_list = (obj *)start_free;439         }440 441         //配置heap空间,用来补充内存池442         start_free = (char *)malloc(bytes_to_get);443         if (0 == start_free)444         {445             //如果heap空间不足,malloc()失败446             int i;447             obj * __VOLATILE *my_free_list, *p;448             //试着检视我们手上的东西。这不会造成伤害。我们不打算尝试配置449             //较小的区块,因为那在多线程机器上容易导致灾难450             //以下搜索适当的free list451             //所谓适当是指“尚有未用区块,且区块够大”之free list452             for (i = size; i <= __MAX_BYTES; i += __ALIGN)453             {454                 my_free_list = free_list + FREELIST_INDEX(i);455                 p = *my_free_list;456                 if (0 != p)457                 {458                     //free list内尚有未用区块459                     //调整free list以释放出未用的区块到内存池460                     *my_free_list = p -> free_list_link;461                     start_free = (char *)p;462                     end_free = start_free + i;463                     // 此时内存池已经有内存了464                     // 递归调用自己,为了修正objs465                     return chunk_alloc(size, nobjs);466                     //注意,任何残余的零头终将被编入适当的free list中备用467                     468                 }469             }470             end_free = 0;  //如果出现意外(山穷水尽,到处都没有内存可用了)471             //调用第一级配置器,看看out-of-memory机制能否尽点力472             start_free = (char *)malloc_alloc::allocate(bytes_to_get);473             //这会导致抛出异常,或内存不足的情况获得改善474         }475         heap_size += bytes_to_get;476         end_free = start_free + bytes_to_get;477         //递归调用自己,为了修正objs478         return chunk_alloc(size, nobjs);479     }480 }481 482 483 484 // 返回一个大小为n的对象,并且有时候会为适当的free list 增加节点485 // 假设n已经适当上调至8的倍数486 // 我们持有allocation lock487 template <bool threads, int inst>488 void* __default_alloc_template<threads, inst>::refill(size_t n)489 {490     int nobjs = 20;  //默认一次分配20个需求大小的区块491     char * chunk = chunk_alloc(n, nobjs); //chunk是分配的空间的开始地址,令其类型为char *,主要是因为一个char的大小正好是一个byte492     obj * __VOLATILE *my_free_list;493     obj * result;494     obj * current_obj, * next_obj;495     int i;496 497     //如果只获得一个区块,这个区块就分配给调用者,free list 无新节点498     if (1 == nobjs) return chunk;499     //否则准备调整free list,纳入新节点500     my_free_list = free_list + FREELIST_INDEX(n);501 502     //以下在chunk空间内建立free list503     result = (obj *)chunk;  //这一块准备返回给客端504     // 以下导引free list 指向新配置的空间(取自内存池)505     506     //由于chunk是char*,所以加上n,就表示走过n个char,507     //一个char正好是一个byte,所以chunk+n现在指向第二个区块508     *my_free_list = next_obj = (obj *)(chunk + n); 509     for (i = 1; ; ++i)510     {511         // 从1开始,因为第0个将返回给客端512         current_obj = next_obj;513         // 每次移动n个char,正好是n个byte,所以正好指向下个区块514         next_obj = (obj *)((char *)next_obj + n);515         if (nobjs - 1 == i)516         {517             // 已经遍历完,此时next_obj指向的内存已经超出我们分配的大小了518             // 不属于我们的内存519             current_obj -> free_list_link = 0;520             break;521         }522         else523         {524             current_obj -> free_list_link = next_obj;525         }526     }527     return result;528 }529 530 531 template<bool threads, int inst>532 void*533 __default_alloc_template<threads, inst>::reallocate(void *p,534                                                     size_t old_sz,535                                                     size_t new_sz)536 {537     void * result;538     size_t copy_sz;539 540     if (old_sz > (size_t) __MAX_BYTES && new_sz > (size_t) __MAX_BYTES)541     {542         return realloc(p, new_sz);543     }544     if (ROUND_UP(old_sz) == ROUND_UP(new_sz)) return p;545     result = allocate(new_sz);546     copy_sz = new_sz > old_sz ? old_sz : new_sz;547     memcpy(result, p, copy_sz);548     deallocate(p, old_sz);549     return result;550 551 }552 553 #ifdef __STL_PTHREADS554     template <bool threads,int inst>555     pthread_mutex_t556     __default_alloc_template<threads, inst>::__node_allocator_lock557         = PTHREAD_MUTEX_INITIALIZER;558 #endif559 560 #ifdef __STL_WIN32THREADS561         template <bool threads, int inst> CRITICAL_SECTION562         __default_alloc_template<threads, inst>::__node_allocator_lock;563 564         template <bool threads, int inst> bool565         __default_alloc_template<threads, inst>::__node_allocator_lock_initialized566             = false;567 #endif568 569 //省略了通用lock的实现(即不使用pthread,也没有win32thread)570 571 template <bool threads, int inst>572 char *__default_alloc_template<threads, inst>::start_free = 0; //设置初始值573 574 template <bool threads, int inst>575 char *__default_alloc_template<threads, inst>::end_free = 0; //设置初始值576 577 template <bool threads, int inst>578 size_t __default_alloc_template<threads, inst>::heap_size = 0; //设置初始值579 580 //初始化16种大小的区块链表为空581 template <bool threads, int inst>582 typename __default_alloc_template<threads, inst>::obj * __VOLATILE583 __default_alloc_template<threads, inst>::free_list[584 # ifdef __SUNPRO_CC585     __NFREELISTS586 # else587     __default_alloc_template<threads, inst>::__NFREELISTS588 # endif589 ] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };590 591 # ifdef __STL_WIN32THREADS592   // Create one to get critical section initialized.593   // We do this onece per file, but only the first constructor594   // does anything.595   static alloc __node_allocator_dummy_instance;596 # endif597 598 # endif /* ! __USE_MALLOC */599 600 #if defined(__sgi) && !defined(__GNUC__) && (_MIPS_SIM != _MIPS_SIM_ABI32)601 #pragma reset woff 1174602 #endif603 604 __STL_END_NAMESPACE605 606 #undef __PRIVATE607 608 #endif /* __SGI_STL_INTERNAL_ALLOC_WJZH_H */609 610 //End
View Code

 

 

 

  

自己动手实现STL 01:内存配置器的实现(stl_alloc.h)