construct与alloc的基本关系在上一篇中已经简单点出
SGI中设计了双层级配置器(alloc)
- 第一级配置器:直接使用malloc 和free 完成内存的配置和释放
- 第二级配置器:以分配内存的大小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 是我自己命名的,就是一个大的区域......如果造成误解请请自便
- enum {_ALIGN = 8}; //表示bounds中区块大小的递增值
- num {_MAX_BYTES= 128}; // 最大的bound中包含的区块大小
- 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
剩余三个全局函数:
- template<class InputIterator,class ForwardIterator> ForwardIterator uninitialized_copy(InputIterator first,InputIteratorlast ,ForwardIterator result); 使用迭代器的容器的全区间构造函数通常有两个过程:1)配置内存区块,包含区间内所有元素 2)使用uninitialized_copy在该内存区块上构造元素 , 即uninitialized_copy函数内部对result处的内存进行调用copy-construct以first-last区间内的元素作为初值进行初始化
- template<class ForwardIterator,class T> ForwardIterator uninitialized_fill( ForwardIterator first, ForwardIterator last ,const T & x); 首先first/last 如今指向的是结果区域的区间forwardIterator,其次x作为初始值来填充到[first,last)的区间内
- 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来进行判断构造函数的有效性,从而完成初始化