STL源码剖析 笔记之二 空间配置器

第二章 空间配置器
SGI也定义有一个符合部分标准、名为allocator的配置器,但SGI自己从未使用,也不建议使用。
它只是把操作符new和delete做一层简单的包装而已。
SGI特殊的空间空间配置器,std::alloc
一般而言,我们习惯的C++的内存配置和释放操作
1.调用new配置内存     alloc::allocate()
2.调用构造            ::construct()
3.调用析构            ::destroy()
4.调用delete释放内存  alloc::deallocate()

配置器定义于<memory>中,在其中<stl_alloc.h>负责内存空间的配置和释放,<stl_construct.h>负责对象的构造和析构。

                  <stl_construct.h>   定义了construct()和destroy()      
<memory>         <stl_alloc.h>        定义了一二级配置器。配置名为alloc
                <stl_uninitialized.h> 定义了一些全局函数,用来填充或复制大块内存区域。
                    un_initialized_copy()     un_initialized_fill()     un_initialized_fill_n()

construct()接受一个指针和一个初值,把初值设定到指针所指的空间上。8.4.5
destroy()有两个版本
1.接受一个指针,准备将该指针所指之物析构掉。直接调用该对象的析构函数即可。
2.接受first和last两个迭代器,准备将[first,last)范围内的所有对象析构掉。
    为了防止对象的析构函数无关痛痒(trivial destructor),先利用value_type获取迭代器所指对象的型别,
在用__type_traits<T>判断是否无关痛痒,__true_type则结束,__false_type则循环访问,并对每个对象调用第一个版本的destroy()。
第二个版本还有针对char*和wchar*的两个特化版。

空间的配置和释放  <stl_alloc.h>
1.向系统堆system heap要求空间。
2.考虑多线程状态
3.考虑内存不足时应变措施            模拟C++的set_new_handler()
4.考虑过多小型区块造成的内存碎片    次级配置器

SGI设计了双层级配置器,第一级配置器直接使用malloc()和free();
第二级配置器采用不同的策略:配置区块大于128bytes,足够大,调用第一级配置器,配置区块小于128bytes,过小,采用复杂的内存池方式。

第一级配置器__malloc_alloc_template剖析  《Effective C++》
以malloc()、free()、realloc()等C函数执行的实际内存操作,并实现类似C++ new handler的机制。
    它不能直接使用C++ new handler机制,因为并非使用::operator new来配置内存。
所谓C++ new handler机制,你可用 要求系统在内存配置需求无法在满足时,调用一个你指定的函数。
注意: SGI使用malloc而并非 ::operator new来配置内存,一个原因是历史因素?二是C++并未提供相应于realloc()的内存配置操作。

注意:   allocate()和realloc()都是在调用malloc()和realloc()不成功后,调用oom_malloc()和oom_realloc()。
    后两者都有内循环,不断调用“内存不足处理例程”,以期在某次调用后,获得足够内存并完成任务。
    如果未设置 “内存不足处理例程”,则直接抛出bad_alloc异常,或利用exit(1)结束程序。

第二级配置器__default_alloc_template剖析 
1.太多小额区块造成的内存的碎片
2.配置时的额外负担overhead
次层配置,采用内存池memory pool管理:
每次配置一大块内存,并维护对应的自由链表free list。下次再有相同大小的内存需求,则直接从自由链表中拨出。
    如果客户端释放小额区块,就由配置器会受到free lists中。
free-lists的结点结构是个联合union。
union obj
{
    union obj* free_list_link;
    char client_data[1];
};
这样可以在该区块不在free list(即用户申请存储数据)的时候,不浪费4个字节的指针空间。

allocate()
首先判断区块大小,大于128bytes,就调用第一级配置器,小于128bytes就检查相应的free list。
如果free list中有可用的区块,就直接拿来用,然后调整free list的指针域。
如果没有可用区块,将区块大小上调至8的倍数,调用refill(),准备为 freelist重新填充空间。
ROUND_UP(n)    FREELIST_INDEX(n)
deallocate()
首先判断区块大小,大于128bytes,就调用第一级配置器,小于128bytes就找出对应的free list,将区块回收。
refill()    
重新填充free lists
当free list中没有可用区块时,调用refill(),准备为free list重新填充空间。新的空间将取自内存池(chunk_alloc())。
缺省取得20个新区块,但是万一内存池空间不足,获得的结点数可能小于20。 
如果只获得一个区块,则分配给调用者使用,free list无新节点。否则,调整free list,纳入新结点。
chunk_alloc()
从内存池中取空间给free list使用。
上述的chunk_alloc() 函式以 end_free - start_free 來判断内存池的水量。
1.如果水量充足,就直接拨出20個区块传回给free list。
2.如果水量不足以提供20个区块,但还足夠供应一个以上的区块,就撥出這不足20個區塊的空間出去。
這時候其pass by reference 的nobjs參數將被修改為實際能夠供應的區塊數。
3.如果記憶池連一個區塊空間都無法供應,對客端顯然無法交待,首先把内存池的残余零头配给适当的freelist.然后此時便需利用 malloc()從heap中配置内存池,為内存池注入活水源頭以應付需求。新水量的大小為需求量的兩倍,再加上一個隨著配置次數增加而愈來愈大的附加量。 
4.萬一山窮水盡,整個 system heap空間都不夠了(以至無法為内存注入活水源頭),malloc() 行動失敗,
chunk_alloc() 就四處尋找有無「尚有未用區塊,且區塊夠大」之free lists 。找到的話就挖一塊交出,找不到的話就呼叫第一級配置器。
第一級配置器其實也是使用malloc() 來配置記憶體,但它有out-of-memory處理機制(類似new-handler  機制),或許有機會釋放其他的記憶體拿來此處使用。如果可以,就成功,否則發出 bad_alloc 異常。 
最后任何得到内存的情况,均需递归调用自己,以完成njobs的修正。

以上便是整個第二級空間配置器的設計。

STL定義有五個全域函式,作用於未初始化空間上。前兩個函式是用於建構的 construct()和用於解構的 destroy(),另三個函式是uninitialized_copy(),uninitialized_fill(),uninitialized_fill_n(),分別對應於高階函式copy()、fill()、fill_n()。
如果你要使用本節的三個低階函式,應該含入 <memory> ,不過SGI把它們實際定義於 <stl_uninitialized> 。

此3个函数均可使我们能够将内存的配置与对象的构造行为分离开来。
commit or rollback,要么完成,要么回滚。
1.template <class InputIterator, class ForwardIterator>
inline ForwardIterator
  uninitialized_copy(InputIterator first, InputIterator last,
                     ForwardIterator result) ;
    如果作为输出目的地的[result,result+(last-first))范围内每个迭代器都指向未初始化区域,则调用copy constructor,
给作为输入来源的[first,last)范围内每一个对象产生一份复制品,放进输出范围中。
    POD意指Plain Old Data,也就是标量型别或传统的C struct型别。
POD型别必然拥有trivial ctor/dtor/copy/assignment函数。
    首先,取出迭代器first的value type,判断是否为POD。
是POD(__true_type)则采用最有效率的初值填写法,copy();不是POD(__false_type),则采用最保险安全的方法,调用construct()逐个构造。
针对char*和wchar_t*,采用最具效率的做法memmove(),于是有了两个特化版本。
2.template <class ForwardIterator, class T>
inline void uninitialized_fill(ForwardIterator first, ForwardIterator last, 
                               const T& x) ;
    如果[first,last)范围内的每个迭代器都指向未初始化的内存,那么会在该范围内长生x的复制品。
    首先,取出迭代器first的value type,判断是否为POD。
是POD(__true_type)则采用最有效率的初值填写法,fill();不是POD(__false_type),则采用最保险安全的方法,调用construct()逐个构造。
3.template <class ForwardIterator, class Size, class T>
inline ForwardIterator uninitialized_fill_n(ForwardIterator first, Size n,
                                            const T& x) ;
    如果[first,first+n)范围内的每个迭代器都指向未初始化的内存,那么会在该范围内长生x的复制品。
    首先,取出迭代器first的value type,判断是否为POD。
是POD(__true_type)则采用最有效率的初值填写法,fill_n();不是POD(__false_type),则采用最保险安全的方法,调用construct()逐个构造。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值