stl提供了六大组件,其中之一的便是配置器。配置器的作用是负责空间配置和管理,从实现的角度看,配置器是一个实现了动态空间配置、空间管理、空间释放的类模板。stl提供的配置器配置对象为内存,而非硬盘空间或其他空间。
SGI特殊空间配置器,std::alloc
stl的配置器标准与规范不同,其名称为alloc而非allocator(虽然SGI也定义有一个符合部分标准的配置器allocator,但SGI不使用它,主要原因是效率不佳,它只是基层内存配置/释放(new和delete)的一层薄包装,无任何效率强化)。
在我的上一篇博客中介绍了c++内存分配中 new 和 delete 的内部原理。构造一个对象时,包含两阶段操作:(1)调用::operator new配置内存;(2)调用该对象的构造函数构造对象内容。delete销毁一个对象时,也包含两阶段操作:(1)调用该对象的析构函数将对象析构;(2)调用::operator delete释放内存。
为了精密分工,STL allocator决定将两段操作分开:
内存配置由alloc:allocate( )负责,内存释放由alloc:deallocate( )负责
对象构造由::construct( )负责,对象析构由::destroy( )负责
实际上就是吧原来的new 和 delete 的操作再一步进行分解。
构造和析构基本工具:construct()与destroy()
下面是
#include <new.h> //placement new头文件 template <class T1, class T2> inline void construct(T1* p, const T2& value) { new (p) T1(value); //placement new;调用T1::T1(value) } //以下是destroy()第一个版本,接受一个指针 template <class T> inline void destroy(T* pointer) { pointer->~T(); } //destory()第二版本,接收两个迭代器。此函数没法找出元素数值型别 template <class ForwardIterator> inline void destroy(ForwardIterator first, ForwardIterator last) { __destroy(first, last, value_type(first)); }
上述construct()接收一个指针和一个value值,该函数作用是将处之泰然分配到指针所指空间。其实construct()作用只是调用了placement new,创建一个对象。
destroy()有2个版本:
第一版本接受一个指针,将指针所指析构掉
第二版本接受2个迭代器,将范围内的所有对象析构掉,析构前,会先判断析构函数是否无关痛痒(暂时理解为没有内存使用),如果是,则什么都不做就结束,如果不是,则遍历每一个对象调用第一个版本的destroy( )函数
空间配置与释放,std::alloc
C++内存配置基本操作是::operator new(),内存释放基本操作是::operator delete()。这两个全局函数相当于C的malloc()和free()函数。正式如此,SGI以malloc和free完成内存的分配和释放。
考虑到小型区块可能造成的内存破碎问题,SGI设计了双层配置器,第一级配置器直接使用malloc()和free(),第二级配置器采取以下策略:当配置区块超过128bytes时,调用第一级配置器;当配置区块小于128bytes时,采用内存池方式。
第一级配置器
第一级空间配置器使用malloc()、free()、realloc()等c函数执行实际内存配置、释放、重配等操作,当分配的空间大小超过128 bytes的时候使用第一级空间配置器;
static void * allocate(size_t n) { void *result = malloc(n); //直接使用malloc() if (0 == result) result = oom_malloc(n); return result; } static void deallocate(void *p, size_t /* n */) { free(p); //直接使用free() } static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz) { void * result = realloc(p, new_sz); //直接使用realloc() if (0 == result) result = oom_realloc(p, new_sz); return result; }
第二级配置器
当配置区块小于128bytes时,太多小额区块容易造成内存碎片,不易管理,且额外开销增大。SGI的做法是以内存池(memory pool)管理:每次配置一大块内存,并维护对应自由链表(free-list)。下次如若再有相同大小的内存需求,就直接从free-lists中拨出。如果客户端释还了小额区块,就由配置器回收到free-lists中,配置器除了负责配置也方便回收。在分配内存时,会将大小向上调整为8的倍数,因为free-list中的节点大小全是8的倍数。
空间配置函数allocate()
如果申请内存大于128 bytes,就调用第一级配置器,否则说明申请内存小于128 bytes。根据申请内存的大小n在16个free lists中找出其对应的my_free_list,如果对应的my_free_list中没有空闲区块,分配器首先将申请内存的大小上调至8的倍数n,调用refill()(之后介绍),准备重新填充my_free_list,否则说明有可用的空闲区块,更新my_free_list并将第一块空闲区块返回
空间释放函数deallocate()
该函数首先判断区块大小,大于128bytes就调用第一级配置器,小于128bytes就找出对应的free list,将区块回收。
重新填充free lists:refill()函数
当allocate()发现free list中没有可用区块时,会调用refill函数,refill函数会调用chunk_alloc()函数,向内存池取20个结点(取得的结点数可能少于20),当取得的结点数为1个时,则直接配置给客户,free list不变,当取得的结点数大于1个时,则将第一个结点分配给客户,剩下的加入到free list对应的位置中
内存池
chunk_alloc()函数以end_free - start_free来判断内存池的水量,若水量充足,足以供应1个以上的区块,则拨出这些空间出去,若水量不足,一个空间都分配不了,则运用malloc从heap中配置内存(新内存的大小为需求量的两倍再加上一个随配置次数增大的附加量)万一山穷水尽,当system heap空间都不够了,malloc()行动失败,则chunk_alloc()会寻找其它未用过的且够大的区块,找到了就交出,找不到了就调用一级配置器,尽管一级配置器也是malloc()配置的,但它有out-of-memory处理机制,有机会释放其它内存拿来此处使用