最近面试c++的时候,被问到c++内存管理模型,没用过。回来看了下《STL源码解析》,这里简单总结下,不涉及具体实现。感兴趣同学可以下载电子书自己看下。

SGI版本的空间适配器设计哲学:

  1. 向system heap 要求空间。
  2. 考虑多线程状态。
  3. 考虑内存不足时的应变措施。
  4. 考虑过多“小型区块”可能造成的内存碎片(fragment)问题。

考虑到小型区块可能造成的内存碎片问题,SGI设计了双层级配置器,第一级配置器直接使用malloc()和free(),第二级配置器则视情况采用不用的策略:当配置区块超过128字节时,视之为"足够大",便调用第一级配置器;当配置区块小于128字节时,视之为“过小”,为了降低额外负担,便采用复杂的内存池整理方式,而不再求助于第一级配置器。

一、二级配置器的关系,接口包装如下图:

面试浅谈 c++ 的空间两级配置器_内存配置器

第一级配置器以malloc(), free(), realloc()等C函数执行实际的内存配置、释放、重配置操作。

第二级配置器多了一些机制,避免太多小额区块造成内存的碎片。小额区块带来的其实不仅是内存碎片,配置时的额外负担也是一个大问题。额外负担永远无法避免,但是区块愈小,额外负担所占的比例就愈大,愈显得浪费。这里的额外负担指的是分配的内存需要一段信息保存内存的大小、下一个节点指针等额外信息。

SGI第二级配置器的做法是,如果区块够大,超过128字节时,就移交第一级配置器处理。当区块小于128字节时,则以内存池管理,此法又称为次层配置:每次配置一大块内存,并维护对应之自由链表(free-list)。下次若再有相同大小的内存需求,就直接从free-lists中拔出。如果客户端释放小额区块,就由配置器回收到free-lists中。为了方便管理,SGI第二级配置器会主动将任何小额区块的内存需求量上调至8的倍数,并维护16个free-lists,各自管理大小为8,16,24,32....128的小额区块。逻辑图如下:

面试浅谈 c++ 的空间两级配置器_C++_02

分配内存首先判断区块大小,大于128字节就直接调用第一级配置器,小于128字节就检查对应的自由链表free-lists。如果自由链表中有可用的区块,就直接拿来使用,如果没有可用区块,则需要重新填充空间。释放的时候,首先判断区块大小,大于128字节就调用第一级配置器,小于128就找出对应的自由链表,将区块回收。

当发现自由链表中没有可用区块了时,需要为自由链表重新填充空间。新的空间将取自内存池。缺省取得20个新节点,但万一内存池空间不足,获得节点数可能小于20。内存池如果水量充足,则直接分配20个区块给free-list 。如果水量不足20个,但超过1个以上,就拨出这不足20个区块的空间出去。如果内存池连一个区块空间都无法供应,此时需要利用malloc()从heap中配置内存,为内存池注入活水以应付需求。新水量的大小为需求量的两倍,再加上一个随着配置次数增加而愈来愈大的附加量。

万一山穷水尽,整个system heap空间不够了,malloc行动失败,则四处查找有无“尚有未用区块,且区块够大”的free list,找到了就挖一块,找不到就调用第一级配置器。第一级配置器其实也是使用malloc来配置内存,但它有out-of-memory处理机制,或许有机会释放其它的内存拿来此处使用,如果可以,就成功,否则发出bad_alloc异常。

以上就是整个第二级空间配置器的设计。

总结来说:第一级负责分配大内存,直接使用malloc,第二级使用内存池加上自由链表来分配。

参考资料:

1. 《STL源码剖析》侯捷

=============================================================================================

Linux应用程序、内核、驱动、后台开发交流讨论群(745510310),感兴趣的同学可以加群讨论、交流、资料查找等,前进的道路上,你不是一个人奥^_^。