1、空间配置器

看侯捷老师的《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 }
时间: 2024-08-07 12:34:46

1、空间配置器的相关文章

【C++/STL】list的实现(没有采用迭代器和空间配置器所实现的双向链表的基本功能)

<span style="font-size:18px;">#include <iostream> using namespace std; //没有采用迭代器和空间配置器所实现的双向链表的基本功能 template<class _Ty> //定义模板类 class list //list类 { public: typedef size_t size_type; //类型重定义 protected: struct _Node; //结构体_Node

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

前言 以STL的实现角度而言,第一个需要介绍的就是空间配置器,因为整个STL的操作对象都存放在容器之中. 你完全可以实现一个直接向硬件存取空间的allocator. 下面介绍的是SGI STL提供的配置器,配置的对象,是内存.(以下内容来自<STL源码剖析>) 空间配置器的标准接口 根据STL的规范,allocator的必要接口 各种typedef 1 allocator::value_type 2 allocator::pointer 3 allocator::const_pointer 4

Ch2 空间配置器(allocator) ---笔记

2.1 空间配置器的标准接口 allocator的必要接口: allocator::value_type allocator::pointer allocator::const_pointer allocator::reference allocator::const_reference allocator::size_type allocator::difference_type //一个嵌套的class template(类模板), //class rebind<U>拥有唯一成员other

STL空间配置器那点事

STL简介 STL(Standard Template Library,标准模板库),从根本上说,STL是一些“容器”的集合,这些“容器”有list,vector,set,map等,STL也是算法和其他一些组件的集合. 谈及组件,那么我们就首先来简单谈下STL六大组件,其相关的设计模式使用,以及各组件之间的协作关系. 设计模式一览 六大组件简单介绍 1. 空间配置器:内存池实现小块内存分配,对应到设计模式--单例模式(工具类,提供服务,一个程序只需要一个空间配置器即可),享元模式(小块内存统一由

SGI的特殊空间配置器

SGI的空间配置器allocator只是简单的new和delete的一层包装,没有提供效率的强化. 而一般C++内存配置和释放操作如下: class  Foo  { ... } Foo  *pf = new Foo; delete pf; new算式:1)使用new配置内存,2)使用Foo构造对象 delelte算式: 1)使用~Foo()将对象析构 ,2)使用delete释放内存 STL allocator 将这两阶段操作区分开来.内存配置操作由 alloc::allocate() 负责,内存

【C++/STL】list的实现(采用空间配置器和迭代器)

在list库函数的编译中仍然有很多问题,在源代码的编译中有些内容尚未搞懂,在后期的学习中会进行更加深入的学习,希望大家可以对我的问题提出建议和批评,谢谢大家~ 具体的代码如下: #include <iostream> using namespace std; //采用迭代器和空间配置器所实现的双向链表的基本功能 template<class _Ty,class _A = allocator<_Ty> > //定义模板类 class list //list类 { publ

STL源码剖析 --- 空间配置器 std::alloc

STL是建立在泛化之上的.数组泛化为容器,参数化了所包含的对象的类型.函数泛化为算法,参数化了所用的迭代器的类型.指针泛化为迭代器,参数化了所指向的对象的类型.STL中的六大组件:容器.算法.迭代器.配置器.适配器.仿函数. 这六大组件中在容器中分为序列式容器和关联容器两类,正好作为STL源码剖析这本书的内容.迭代器是容器和算法之间的胶合剂,从实现的角度来看,迭代器是一种将operator*.operator->.operator++.operator-等指针相关操作予以重载的class tem

stl空间配置器线程安全问题补充

摘要 在上一篇博客<STL空间配置器那点事>简单介绍了空间配置器的基本实现 两级空间配置器处理,一级相关细节问题,同时简单描述了STL各组件之间的关系以及设计到的设计模式等. 在最后,又关于STL空间配置的效率以及空间释放时机做了简单的探讨. 线程安全问题概述 为什么会有线程安全问题? 认真学过操作系统的同学应该都知道一个问题. first--进程是系统资源分配和调度的基本单位,是操作系统结构的基础,是一个程序的运行实体,同时也是一个程序执行中线程的容器 seconed--进程中作为资源分配基

STL源码剖析--空间配置器

STL的设计非常巧妙,组件间互取短长,形成了一个世界,这是这个世界里的组件: 1. containers(容器):所谓容器,是指存放数据的地方,将数据以一定的方法组织存放.根据不同的组织方式,可以把容器分为顺序容器,如vector.deque.list,关联容器,如set.map.Container是一种class template. 2. algorithm(算法):各种常用不常用的算法如sort.copy.search等等.algorithm是一种function template. 3.