STL源码剖析:【2】空间配置器-stl_alloc.h

construct与alloc的基本关系在上一篇中已经简单点出

SGI中设计了双层级配置器(alloc)

  1. 第一级配置器:直接使用malloc 和free 完成内存的配置和释放
  2. 第二级配置器:以分配内存的大小128bytes(记住这个显著的分界线) 决定调用第一级配置器,还是自身进行内存的分配

一个全局定义_USE_MALLOC: 决定将第一级(_malloc_alloc_template)还是第二级配置器(_default_alloc_template)作为默认alloc(因为在容器中许多提供了template<class type,class Alloc = alloc>模板声明,这决定了容器进行内存分配的方式)

_malloc_alloc_template(第一级内存配置器):

 private static void(*oom_malloc)(size_t )

{ 如果存在 【3】那么调用,不存在会粗暴的被抛出OutOfMemory异常,否则在无限循环中重新进行malloc请求  }

 private static void (*oom_remalloc)(void *,size_t ) ;

{ 如果存在 【3】那么调用,不存在会粗暴的被抛出OutOfMemory异常,否则在无限循环中重新进行remalloc请求  }

 private static void (*_malloc_alloc_oom_handler【3】) (); 

以上几个函数指针分别作为各种内存申请不足时,回调的函数

static void * allocate(size_t n){void *result = malloc(n); if(result == 0) result = oom_malloc() ; return result;}

static void * deallocate(void *p  , size_t){free(p);}

static void * reallocate(void *p,size_t,size_t new_sz){内容对比allocate}

_default_alloc_template(第二级内存配置器):

 首先讲几个枚举值: 

      二级配置器为了减少内存碎片化,内部维护了一个列表:free_list,其中free_list中的每一个成员bounds中存储的内存大小以8的倍数进行递增,即8/16/24/32/40.........128。

     bound 是我自己命名的,就是一个大的区域......如果造成误解请请自便

  1. enum {_ALIGN = 8};   //表示bounds中区块大小的递增值
  2. num {_MAX_BYTES= 128}; // 最大的bound中包含的区块大小
  3. num {_NFREELISTS =_MAX_BYTES/ _ALIGN }; // 128/8即递增了多少次,就有多少个bound

//以下为Class alloc中的几个成员START

class static data member: free_list[_NFREELISTS];

class size_t FREELIST_INDEX(size_t bytes);  //这个函数根据申请大小来,定位相应bound在free_list中的index

template<bool threads,int inst>

             char * _default_alloc_template<threads,inst>::start_free = 0; // 内部维护堆(这个堆是申请系统堆后交由alloc进行管理的)的剩余自由空间的开始指针

             char * _default_alloc_template<threads,inst>::end_free = 0;//同上结束指针

             char * _default_alloc_template<threads,inst>::heap_size = 0; //配置的Heap的大小,随着申请堆得的增加,逐渐增加

//成员END


因为第二级配置器中函数比较复杂,我将只把大体的思路记下来,有想要看具体数据的请移步STL源码剖析进行查看

  • allocate:对128Bytes以上的内存申请将,使用第一级配置器进行申请,详细看上 ; 小于128Bytes将会在free_list中按照大小n在相应bound进行分配;如果不够了(或没有)将会启动Refill函数重新填充free_list(具体看下面)
  • deallocate :释放相应内存池中使用的内存,将会重新采用头插法插入到相应bound中去。(这种回收机制减少了大量的内存碎片)
  • refill (size_t n):在allocate中,如果bound中区块(我将每一个bound中的list元素单元称为区块)为分配n大小的内存失败,将会调用该函数; 内部调用chunk_allocate 从内存池(alloc内部维护的堆块)中获取默认值为20的区块个数的相应大小的内存,并链接到相应free_list的index处
  • chunk_allocate:内部不仅需要对请求的内存进行分配,还需要对内存池的大小进行动态增加,其中当申请内存失败时。将会遍历free_list中的内存大小,然后释放一部分,用于生成我们申请的大小的bound区块

我截取了书中一块对chunk_allocate调用过程的例子,用来便于理解:

图片来源于STL源码剖析p69

剩余三个全局函数:

  1. template<class InputIterator,class ForwardIterator> ForwardIterator  uninitialized_copy(InputIterator first,InputIteratorlast ,ForwardIterator result); 使用迭代器的容器的全区间构造函数通常有两个过程:1)配置内存区块,包含区间内所有元素 2)使用uninitialized_copy在该内存区块上构造元素 , 即uninitialized_copy函数内部对result处的内存进行调用copy-construct以first-last区间内的元素作为初值进行初始化
  2. template<class ForwardIterator,class T> ForwardIterator  uninitialized_fill( ForwardIterator first, ForwardIterator last ,const T &  x); 首先first/last 如今指向的是结果区域的区间forwardIterator,其次x作为初始值来填充到[first,last)的区间内
  3. template<class ForwardIterator,class size,class T> ForwardIterator  uninitialized_fill_n( ForwardIterator first, size n  ,const T &  x); 首先first 如今指向的是结果区域的区间forwardIterator,其次x作为初始值来填充到[first,first + n)的区间内

首先需要明确的一个观点是这三个全局函数的参数,都是针对已经开辟好的内存空间。

其次三个全局函数由C++标准规格书要求具有Commit or RollBack :要么构造出全部元素对象,如果其中存在一个元素对象初始化失败,那么将会析构所有的已构造元素对象;

最后,注意的是类比于在上文讲过的Construct.h中的has trivial constrcut判断:这三个对内存级别的全局函数需要对拷贝的数据类型Class T1进行判断,copy是对InputIterator 中迭代器指向类型进行判断,fill则是对const & T对象进行判断,判断对象是否为POD(Plain Old Data)即标量型别和传统的cStruct型别;如果是POD对象,则调用Stl的fill()算法,如果是No—POD则根据has trivial construct来进行判断构造函数的有效性,从而完成初始化

发布了12 篇原创文章 · 获赞 1 · 访问量 1270

猜你喜欢

转载自blog.csdn.net/qq_34250367/article/details/105666443