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

今天终于开始了计划很久的看STL源码,过程应该很艰巨,我争取书博客各看一遍,为了防止看了就忘,准备做好笔记,在5天内看完。加油!

一、空间配置器allocator

1.1简介

整个STL的操作对象都存放在容器中,而容器一定需要配置空间置放资料。空间不一定是内存,也可以是磁盘或其他辅助媒介。

C++的内存配置操作和释放操作一般是这样:

class foo{...}

foo* pf=new foo;//配置内存,然后构造函数

delete pf; //将对象析构,然后释放内存

STL allocator将这两部分操作分开了,内存配置由alloc::allocate()负责,内存释放由alloc::deallocate();对象构造由::construct()负责,对象析构由::destruct()负责。

STL allocator配置器定义在包含三个文件:(这里定义了全局函数construct()和destroy(),负责对象的构造和析构)(定义了一、二两级配置器,彼此合作)(里定义了一些全局函数,用来填充(fill)或复制(copy)大块内存数据)

1.2

     在stl_alloc.h中定义了两级配置器。当申请的内存大小大于128byte时,就启动第一级分配器通过malloc直接向系统的堆空间分配,如果申请的内存大小小于128byte时,就启动第二级分配器,从一个预先分配好的内存池中取一块内存交付给用户,这个内存池由16个不同大小(8的倍数,8~128byte)的空闲列表组成,allocator会根据申请内存的大小(将这个大小round up成8的倍数)从对应的空闲块列表取表头块给用户。

第一级配置器_malloc_alloc_template,

主要思想是申请大块内存池(超过128bytes),第一级配置器没有用operator::new和operator::delete来申请空间,而是直接调用malloc/free和realloc(C函数执行的操作),并且实现了类似c++中new-handler的机制。所谓c++ new handler机制是,你可以要求系统在内存配置需求无法被满足时,调用一个指定的函数。换句话说,一旦::operator::new无法完成任务,在丢出std::bad_alloc异常状态之前,会先调用由客端指定的处理例程,该处理例程通常称为new-handler.new-handler解决内存做法有特定的模式。SGI第一级配置器的allocate()和realloc都是在调用malloc和realloc不成功后,改调用oom_malloc()和oom_realloc(),后两者都有内循环,不断调用"内存不足处理例程",期望在某次调用之后,获得足够的内存而圆满完成任务。但如果“内存不足处理例程“并未被客端设定,oom_malloc()和oom_realloc便调用_THROW_BAD_ALLOC, 丢出bad_alloc异常信息,或利用exit(1)硬生生中止程序。

第二级配置器_default_alloc_template

当区块小于128字节时,则以内存池管理,此法又称为次层配置,每次配置一大块内存,并维护对应的自由链表(free-list)。下次若再有相同大小的内存需求,就直接从free-list中拔出。如果客端释还小额区块,就由配置器回收到free-lists中,另外,配置器除了负责配置,也负责回收。为了管理方便,SGI第二级配置器会主动将任何小额区块的内存需求量上调至8的倍数。并维护16个free-lists,各自管理大小分别为8,16,24,32,40,48,56,64,72,80,88,96,104, 112,120,128 字节的小额区块。当申请小于等于128字节时就会检查对应的free list,如果free-list中有可用的区块,就直接拿来,如果没有,就准备为对应的free-list 重新填充空间。

     新的空间将取自内存池(经由chunk_alloc()获得),缺省取得20个新节点,如果内存池不足(还足以一个以上的节点),有多少就返回多少.如果当内存池中连一个节点大小都不够时,此时就需要malloc()从heap中配置内存,新水量的大小为需求量的两倍。如果malloc()行动失败,就会四处寻找有无"尚有未用区块,且区块足够大 "之free lists.找到了就挖一块交出,找不到就调用第一级配置器。第一级配置器其实也是使用malloc来配置内存。但它有out-of-memory处理机制(类似new-handler机制),或许有机会释放其他的内存拿来此处使用。如果可以就成功,否则发出bad_alloc异常。

  1. union obj  
  2. {  
  3.     union obj * free_list_link;  //指针类型是联合体,所以16个指针共享内存,不会占多余的额外内存
  4.     char client_data[1];  
  5. };  
  6. //分配算法,从网上大神那里找来的,觉着写的很简练
  1. // 算法:allocate  
  2. // 输入:申请内存的大小size  
  3. // 输出:若分配成功,则返回一个内存的地址,否则返回NULL  
  4. {  
  5.     if(size 大于 128)  
  6.         启动第一级分配器直接调用malloc分配所需的内存并返回内存地址;  
  7.     else  
  8.     {  
  9.         将size向上round up成8的倍数并根据大小从free_list中取对应的表头free_list_head  
  10.         if(free_list_head 不为空)  
  11.         {  
  12.             从该列表中取下第一个空闲块并调整free_list,返回free_list_head  
  13.         }  
  14.         else  
  15.         {  
  16.             调用refill算法建立空闲块列表并返回所需的内存地址  
  17.         }  
  18.     }  
  19. }  
  20.   
  21.   
  22. // 算法:refill  
  23. // 输入:内存块的大小size  
  24. // 输出:建立空闲块链表并返回第一个可用的内存地址  
  25. {  
  26.     调用chunk_alloc算法分配若干个大小为size的连续内存区域并返回起始地址chunk和成功分配的块数nobj  
  27.     if(块数为1)  
  28.         直接返回 chunk;  
  29.     else  
  30.     {  
  31.         开始在chunk地址块中建立free_list  
  32.         根据size取free_list中对应的表头元素free_list_head   
  33.         将free_list_head 指向chunk中偏移起始地址为size的地址处,即free_list_head = (obj*)(chunk+size)  
  34.         再将整个chunk中剩下的nobj-1个内存块串联起来构成一个空闲列表  
  35.         返回chunk,即chunk中第一个空闲的内存块  
  36.     }  
  37. }  
  38.   
  39.   
  40. // 算法:chunk_alloc  
  41. // 输入:内存块的大小size,预分配的内存块数nobj(以引用传递)  
  42. // 输出:一块连续的内存区域的地址和该区域内可以容纳的内存块的块数  
  43. {  
  44.     计算总共所需的内存大小total_bytes  
  45.     if(内存池足以分配,即end_free-start_free >= total_bytes)  
  46.     {  
  47.         则更新start_free  
  48.         返回旧的start_free  
  49.     }  
  50.     else if(内存池不够分配nobj个内存块,但至少可以分配一个)  
  51.     {  
  52.         计算可以分配的内存块数并修改nobj  
  53.         更新start_free并返回原来的start_free  
  54.     }  
  55.     else     // 内存池连一个内存块都分配不了  
  56.     {  
  57.         先将内存池的内存块链入到对应的free_list中后  
  58.         调用malloc操作重新分配内存池,大小为2倍的total_bytes为附加量,start_free指向返回的内存地址  
  59.         if(分配不成功)  
  60.         {  
  61.             if(16个空闲列表中尚有空闲块)  
  62.                 尝试将16个空闲列表中空闲块回收到内存池中再调用chunk_alloc(size,nobj)  
  63.             else  
  64.                 调用第一级分配器尝试out of memory机制是否还有用  
  65.         }  
  66.         更新end_free为start_free+total_bytes,heap_size为2倍的total_bytes  
  67.         调用chunk_alloc(size,nobj)  
  68.     }  
  69. }  
  70.   
  71.   
  72. // 算法:deallocate  
  73. // 输入:需要释放的内存块地址p和大小size  
  74. {  
  75.     if(size 大于128字节)  
  76.         直接调用free(p)释放  
  77.     else  
  78.     {  
  79.         将size向上取8的倍数,并据此获取对应的空闲列表表头指针free_list_head  
  80.         调整free_list_head将p链入空闲列表块中  
  81.     }  
  82. }  




你可能感兴趣的:(STL源码剖析笔记)