stl源码剖析-空间配置器

  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 的操作再一步进行分解。

 

stl源码剖析-空间配置器_第1张图片

 

 

构造和析构基本工具: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时,采用内存池方式。

stl源码剖析-空间配置器_第2张图片

 

 

 

第一级配置器

  第一级空间配置器使用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的倍数。stl源码剖析-空间配置器_第3张图片

 

空间配置函数allocate()

 

   如果申请内存大于128 bytes,就调用第一级配置器,否则说明申请内存小于128 bytes。根据申请内存的大小n在16个free lists中找出其对应的my_free_list,如果对应的my_free_list中没有空闲区块,分配器首先将申请内存的大小上调至8的倍数n,调用refill()(之后介绍),准备重新填充my_free_list,否则说明有可用的空闲区块,更新my_free_list并将第一块空闲区块返

stl源码剖析-空间配置器_第4张图片

 

 

空间释放函数deallocate()

  该函数首先判断区块大小,大于128bytes就调用第一级配置器,小于128bytes就找出对应的free list,将区块回收。  

stl源码剖析-空间配置器_第5张图片

 

 

重新填充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处理机制,有机会释放其它内存拿来此处使用

stl源码剖析-空间配置器_第6张图片

 

 

stl源码剖析-空间配置器_第7张图片

 

 

 

你可能感兴趣的:(stl源码剖析-空间配置器)