首页 > 其他 > 详细

1、空间配置器

时间:2015-11-18 22:41:16      阅读:312      评论:0      收藏:0      [点我收藏+]
看侯捷老师的《STL源码剖析》有一段时间了,打算自己整理一下思路,试着实现一下。主要目的有两个:1、巩固自己对源码的理解,让自己更加深刻的体会其中各种机制的奥妙。2、通过实现这些优秀的算法,来提高自己的“内功”修养。
 
关于空间配置器,首先作以下几点说明:
1、空间配置器即为程序分配存储空间。这里的存储空间包括内存,也包括磁盘或者其它辅助存储介质。
2、C++一般使用new算式进行存储空间配置。使用delete操作符释放分配的内存。其中的new算式内含两个阶段操作:(1)、调用::operator new配置内存;(2)、调用Foo::Foo()构造对象内容。delete也包含两阶段操作:(1)、调用Foo::~Foo()将对象析构;(2)、调用::operator delete释放内存。
3、在SGI STL中,空间配置器分为两级。第一级配置器直接使用malloc()与free()。第二级配置器则是情况采用不同的策略:当配置空间大于128kb时,采用第一级配置器。当需要分配的空间小于128时,则采用内存池的方式。其目的是为了降低额外负担,减少内存碎片。
好了,废话不多说,直接上代码:
 
以下是一级配置器的头文件,具体实现代码注释非常详细,这里不再赘述。
#ifndef SRC_FRISTCONFIGURATOR_H_
#define SRC_FRISTCONFIGURATOR_H_

#include "comm.h"

/*一级配置器,采用malloc、free、realloc等实际操作内存*/
class CFristConfigurator{
public:
    CFristConfigurator();
    virtual ~CFristConfigurator();

    /*分配内存,该函数内将会直接调用malloc函数*/
    static void *Allocate(size_t uiSzie);

    /*重新分配内存。该函数中将会直接调用realloc函数*/
    static void *Reallocate(void *pOld, size_t uiOldSize, size_t uiNewSize);

    /*撤销分配的内存。这里的参数n只是预留参数*/
    static void    Dellocate(void *p, size_t n);

    /*设置历程函数指针。用户可以通过这个函数去设置历程*/
    static void (*SetMallocAllocOomHandler(void (*vpFunc)()))();

private:
    /*当malloc失败后,将会调用这个函数继续分配空间*/
    static void *OomMalloc(size_t uiSize);

    /*当realloc调用失败后,会调用该函数继续分配空间*/
    static void *OomRealloc(void *pOld, size_t uiNewSize);

    /*该指针用于保存空间分配失败的例程函数指针。用它可以实现C++的new handler机制*/
    static void (*ms_pMmallocAllocOomHandler)();
};



#endif

 

以下是一级配置器代码。需要注意的是处理例程是自己编写的。处理例程有其固定的模式。
  1 #include "FristConfigurator.h"
  2 #include <unistd.h>
  3 
  4 //这里将函数指针设置为0,等待客户端去设置。这里的处理例程都是由客户端编写并指定的
  5 void (*CFristConfigurator::ms_pMmallocAllocOomHandler)() = 0;
  6 
  7 CFristConfigurator::CFristConfigurator() {
  8     // TODO Auto-generated constructor stub
  9 
 10 }
 11 
 12 CFristConfigurator::~CFristConfigurator() {
 13     // TODO Auto-generated destructor stub
 14 }
 15 
 16 void *CFristConfigurator::Allocate(size_t uiSize)
 17 {
 18     void *vpResult = malloc(uiSize);    //一级配置器,直接调用函数malloc
 19     if(0 == vpResult)
 20     {
 21         vpResult = OomMalloc(uiSize);    //如果配置失败,则直接调用另一个函数配置
 22     }
 23 
 24     return vpResult;
 25 }
 26 
 27 void *CFristConfigurator::Reallocate(void *vpOld, size_t uiOldSize, size_t uiNewSize)
 28 {
 29     void *vpResult = realloc(vpOld, uiNewSize);//一级配置器,直接调用realloc
 30     if(0 == vpResult)
 31     {
 32         vpResult = OomRealloc(vpOld, uiNewSize);//当realloc分配失败后会调用另外一个函数去重复分配
 33     }
 34 
 35     return vpResult;
 36 }
 37 
 38 void CFristConfigurator::Dellocate(void *p, size_t uiStandby = 0)//uiStandby参数是一个备用参数
 39 {
 40     free(p);//一级配置器直接调用free
 41 }
 42 
 43 void (*CFristConfigurator::SetMallocAllocOomHandler(void (*vpFunc)()))()
 44 {
 45     void (*vpOldFunc)() = ms_pMmallocAllocOomHandler;//保存原来的处理例程
 46     ms_pMmallocAllocOomHandler = vpFunc;
 47     return vpOldFunc;//将原来的处理例程返回给客户端,以便保存
 48 }
 49 
 50 void *CFristConfigurator::OomMalloc(size_t uiSize)
 51 {
 52     void (*vpMyMmallocAllocOomHandler)();
 53     void *vpResult;
 54 
 55     while(1)//这里会一直重复配置空间。不断尝试释放、配置、再释放、再配置...直到配置成功为止
 56     {
 57         vpMyMmallocAllocOomHandler = ms_pMmallocAllocOomHandler;
 58         if(0 == vpMyMmallocAllocOomHandler)//如果客户端没有设置处理例程,则将抛出异常
 59         {
 60             //抛出异常
 61         }
 62         /*之前想的是:如果这里没有分配成功,则说明在非常短的时间内不会分配到内存,
 63          *在这里稍作睡眠,可以将该进程调出CPU,让CPU运行其它进程,CPU也可以不用
 64          *做“无谓的”循环,提高效率。但是这样做是不正确的,原因是:这里根本不知道
 65          *调用这里的程序的紧急性是不是如果很高,这里的睡眠虽然让整个系统有一点效
 66          *率的提高,但是这样做很可能会让调用这个函数的程序失去处理其它事件的机会,
 67          *从而达不到想要的结果。因此这里做睡眠是相当错误的选择。事实上,在
 68          *vpMyMmallocAllocOomHandler指向的函数中,也会去企图释放掉一些内存,这
 69          *可比睡眠方式好得多(记录下来自己曾经的想法)*/
 70         //usleep(1);
 71         //这里使用vpMyMmallocAllocOomHandler而不是使用ms_pMmallocAllocOomHandler
 72         //调用处理例程,个人认为是出于对程序指针的保护。以免原来函数指针被修改
 73         (*vpMyMmallocAllocOomHandler)();//调用处理历程,企图从其它地方释放内存
 74         vpResult = malloc(uiSize);
 75         if(0 != vpResult)
 76         {
 77             return vpResult;
 78         }
 79     }
 80 
 81     return 0;//纯属为了消除警告
 82 }
 83 
 84 void *CFristConfigurator::OomRealloc(void *vpOld, size_t uiNewSize)
 85 {
 86     void (*vpMyMmallocAllocOomHandler)();
 87     void *vpResult;
 88     while(1)
 89     {
 90         vpMyMmallocAllocOomHandler = ms_pMmallocAllocOomHandler;
 91         if(0 == vpMyMmallocAllocOomHandler)
 92         {
 93             //抛出异常
 94         }
 95         (*vpMyMmallocAllocOomHandler)();
 96         vpResult = realloc(vpOld, uiNewSize);
 97         if(0 != vpResult)
 98         {
 99             return vpResult;
100         }
101     }
102     return 0;
103 }

 

以下是二级配置器的头文件:

 1 #ifndef SRC_DEFAULTALLOCTEMPLATE_H_
 2 #define SRC_DEFAULTALLOCTEMPLATE_H_
 3 
 4 #include "comm.h"
 5 
 6 enum {enALIGN = 8};        //小型区块的上调边界
 7 enum {enMAX_BYTES = 128};        //小型区块的上限
 8 enum {enNFREELISTS = enMAX_BYTES / enALIGN};    //free-list个数(每个上调边界一个free-list)。
 9 
10 /*这个共用体为每个小额内存块的结构。这种定义方法将不会为了维护链表
11  *所必须的指针而造成浪费。这个小小的技巧节省下来的内存是相当可观的*/
12 union unObj
13 {
14     union unObj * FreeListLink;
15     char cCloendData[1];
16 };
17 
18 /*二级配置:为了减少小额区块造成的内存碎片,以及减轻内存配置时的额外负担,
19  *SIG使用了二级配置器。以下是二级配置器类的定义*/
20 /*template <bool threads, int inst> //这里的threads参数是用于多线程,inst没有使用。为了书写简便,故将其注释*/
21 class CDefaultAllocTemplate {
22 public:
23     CDefaultAllocTemplate();
24     virtual ~CDefaultAllocTemplate();
25 
26     /*空间配置函数*/
27     static void *Allocate(size_t uiSize);
28 
29     /*空间释放函数*/
30     static void Deallocate(void *p, size_t uiSize);
31 
32     /*填充free-list函数*/
33     static void *Reallocate(void *p, size_t uiOldSize, size_t uiNewSize);
34 
35 private:
36 
37     /*为了便于管理,将小额区块的内存需求量上调为8的倍数*/
38     static size_t RoundUp(size_t uiBytes)
39     {
40         return (((uiBytes) + enALIGN - 1) & ~(enALIGN - 1));
41     }
42 
43     /*这个函数将会确定使用哪个free-list*/
44     static size_t FreeListIndex(size_t uiBytes)
45     {
46         return (((uiBytes) + enALIGN) / enALIGN - 1);
47     }
48 
49     /*当free-list不足时,将调用该函数为free-list重新填充。如果内存池只能提供一个块,
50      *那么将会把这一个块返回给调用者,而free-list得不到填充*/
51     static void *Refill(size_t uiSize);
52 
53     /*配置一个大空间,即内存池。内存池的作用是为free-list填充元素*/
54     static char *ChunkAlloc(size_t uiSize, int &iNobjs);
55 
56 private:
57     /*用于存放16个free-list*/
58     static unObj * volatile ms_pFreeList[enNFREELISTS];
59 
60     /*内存起始地址,只在ChunkAlloc函数中变化*/
61     static char *ms_cpStartFree;
62 
63     /*内存结束地址,只在ChunkAlloc函数中变化*/
64     static char *ms_cpEndFree;
65 
66     /*记录内存池中分配内存空间的大小*/
67     static size_t uiHeapSize;
68 };
69 
70 /*赋初值*/
71 unObj * volatile CDefaultAllocTemplate::ms_pFreeList[enNFREELISTS] =
72     {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
73 char *CDefaultAllocTemplate::ms_cpStartFree = 0;
74 char *CDefaultAllocTemplate::ms_cpEndFree = 0;
75 size_t CDefaultAllocTemplate::uiHeapSize = 0;
76 
77 #endif

二级配置器的源文件

  1 #include "DefaultAllocTemplate.h"
  2 #include "FristConfigurator.h"
  3 
  4 CDefaultAllocTemplate::CDefaultAllocTemplate() {
  5     // TODO Auto-generated constructor stub
  6 }
  7 
  8 CDefaultAllocTemplate::~CDefaultAllocTemplate() {
  9     // TODO Auto-generated destructor stub
 10 }
 11 
 12 void *CDefaultAllocTemplate::Allocate(size_t uiSize)
 13 {
 14     unObj * volatile *pMyFreeList;
 15     unObj *pResult;
 16 
 17     if(uiSize > enMAX_BYTES)    //当需要分配的内存大于128时,调用一级配置器
 18     {
 19         return CDefaultAllocTemplate::Allocate(uiSize);
 20     }
 21 
 22     /*得到需要使用的free-list*/
 23     pMyFreeList = ms_pFreeList + CDefaultAllocTemplate::FreeListIndex(uiSize);
 24     pResult = *pMyFreeList;
 25     if(0 == pMyFreeList)
 26     {
 27        
 28         void *pRet = Refill(uiSize);//没有找到,需要重新装填free-list,装填后分配
29 return pRet; 30 } 31 else 32 { 33 *pMyFreeList = pResult->FreeListLink;//取出一块内存后需要调整链表 34 return (pResult); 35 } 36 } 37 38 void CDefaultAllocTemplate::Deallocate(void *pFree, size_t uiSize) 39 { 40 unObj *pFreeObj = (unObj *)pFree; 41 unObj *volatile *pMyFreeList; 42 43 if(uiSize > enMAX_BYTES)//如果要释放的空间大于enMAX_BYTES,则直接调用一级配置器的释放函数 44 { 45 CFristConfigurator::Dellocate(pFree, uiSize); 46 return ; 47 } 48 else 49 { 50 pMyFreeList = ms_pFreeList + FreeListIndex(uiSize);//确定使用的free-list 51 pFreeObj->FreeListLink = *pMyFreeList;//将内存块回收到free-list中 52 *pMyFreeList = pFreeObj; 53 } 54 } 55 56 void *CDefaultAllocTemplate::Refill(size_t uiSize) 57 { 58 int iNobjs = 20;//填充个数默认设置为20,当内存池空间不足时,可能会小于20个 59 60 /*为free-list填充空间,最终填充个数存放在参数iNobjs中.其返回值将会当作申请的块返回给调用者*/ 61 char *cpChunk = ChunkAlloc(uiSize, iNobjs); 62 63 unObj *volatile *pMyFreeList; 64 unObj *pResult; 65 unObj *pCurrentObj, *pNextObj; 66 67 if(1 == iNobjs)//如果只有一个块,将会把这个块返回给调用者 68 { 69 return cpChunk; 70 } 71 else//大于一个块时,将会调整free-list,纳入新的块 72 { 73 pMyFreeList= ms_pFreeList + FreeListIndex(uiSize);//确定需要调整的free-list 74 pResult = (unObj *)cpChunk;//这一块将会返回给调用者 75 76 /*将剩下的块添加到free-list中*/ 77 /*每一个块大小是uiSize,这里相当于跳过第一个块。其他的将会添加到free-list中。 78 *这里实质上是将一整块内存当作链表操作。目的是为了管理内存*/ 79 *pMyFreeList = pNextObj = (unObj *)(cpChunk + uiSize); 80 for(int i = 1; ; i++) 81 { 82 pCurrentObj = pNextObj; 83 pNextObj = (unObj *)((char *)(pNextObj + uiSize));//这里相当于指向下一个块内存 84 if(iNobjs - 1 == i)//将剩下所有块都添加到free-list中 85 { 86 pCurrentObj->FreeListLink = 0; 87 break; 88 } 89 else//没有添加完,继续添加 90 { 91 pCurrentObj->FreeListLink = pNextObj; 92 } 93 } 94 } 95 96 return pResult; 97 } 98 99 char *CDefaultAllocTemplate::ChunkAlloc(size_t uiSize, int &iNobjs) 100 { 101 char *cpResult; 102 size_t uiTotal = uiSize * iNobjs;//需要分配的内存空间 103 size_t uiResidue = ms_cpEndFree - ms_cpStartFree;//内存池中剩余空间 104 105 if(uiResidue >= uiSize)//内存池中内存量不足,但能分配一些块 106 { 107 iNobjs = uiResidue / uiSize;//能够分配的块数 108 cpResult = ms_cpStartFree;//尽其所能分配一定量的空间 109 ms_cpStartFree += iNobjs * uiSize;//调整内存池中起始地址 110 return cpResult; 111 } 112 else if(uiTotal >= uiSize)//如果内存池中的内存完全满足需要,则直接补充 113 { 114 cpResult = ms_cpEndFree;//分配内存空间 115 ms_cpStartFree += uiTotal;//调整内存池的起始地址 116 return cpResult; 117 } 118 else//内存池中剩余量连一块都不能分配 119 { 120 /*如果内存池中还有残存空间,则将剩余的空间全部利用,分配到free-list中*/ 121 if(uiResidue > 0) 122 { 123 /*FreeListIndex函数会计算出内分配的最大块,若内存池剩余量为enMAX_BYTES,则应该为第一种情况 124 *因此这样计算添加哪个free-list非常合理的*/ 125 unObj * volatile * pMyFreeList = ms_pFreeList + FreeListIndex(uiResidue); 126 127 /*调整free-list*/ 128 ((unObj *)(ms_cpStartFree))->FreeListLink = *pMyFreeList; 129 *pMyFreeList = (unObj *)(ms_cpStartFree); 130 } 131 132 /*计算此次需要分配的内存空间大小*/ 133 size_t uiBytes = 2 * uiTotal + RoundUp(uiHeapSize >> 4); 134 ms_cpStartFree = (char *)malloc(uiBytes);//调用malloc分配内存 135 if(0 == ms_cpStartFree)//malloc调用失败。即堆空间没内存可以分配了 136 { 137 unObj * volatile *pMyFeeList, *pP; 138 139 /*以下的代码将会把原来free-list中所有没有被分配的内存释放出来,试图满足调用者的需求*/ 140 for(int i = 0; i <= enMAX_BYTES; i += enALIGN) 141 { 142 pMyFeeList = ms_pFreeList + FreeListIndex(i); 143 pP = *pMyFeeList; 144 if(0 != pP) 145 { 146 *pMyFeeList = pP->FreeListLink; 147 ms_cpStartFree = (char *)pP; 148 ms_cpEndFree = ms_cpStartFree + i; 149 150 /*递归调用自己,目的是1、为了修正iNobjs。2、释放了free-list,递归看看是够满足需求。 151 *这里递归调用的结果可能有两个: 152 *1、找到合适的空间,满足了调用者的需求。 153 *2、无论如何都找不到合适的空间了,当调用第一级配置器时,抛出异常*/ 154 return (ChunkAlloc(uiSize, iNobjs)); 155 } 156 } 157 ms_cpEndFree = 0; 158 159 /*已经没有任何内存可以使用了,只有看一级配置器中的out-of-memory机制能不能挤出内存来。 160 *如果客户端没有设置处理例程,这无疑将会抛出异常或者直接暴力结束程序*/ 161 ms_cpStartFree = (char *)CFristConfigurator::Allocate(uiSize); 162 } 163 uiHeapSize += uiBytes; 164 ms_cpEndFree = ms_cpStartFree + uiBytes; 165 166 /*递归调用自己,目的是为了修正iNobjs。 167 *因为调用malloc为内存池补充以后,到底补充了多少,之前并不知道。也就是说,程序之前并不知道补充以后能分配 168 *多少块。因此这调用的目的不仅是修正iNobjs。还要调整内存池*/ 169 return (ChunkAlloc(uiSize, iNobjs)); 170 } 171 172 173 return cpResult; 174 }

 

1、空间配置器

原文:http://www.cnblogs.com/zxtp/p/4975888.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!