你有没这样的经历,想去看STL源码,浏览了几页后,发现犯困,便又放到了一边。我也这样过,但一个偶然的机会,我学习了内存池,突然发现STL也有这样的东西,便再拿起尘封已久的STL源码,发现能看懂了。所有,让我带你突破STL源码的大门吧!
为什么要使用空间配置器呢?如果你学过C++,那你一定懂得,当需要分配内存便使用new和delete,也可以使用malloc和free,不过malloc是不会触发构造函数的调用。内存配置器其实也是调用这些来分配内存,只是它对分配好的内存做了进一步的处理才给我们用,让你程序更高效。下面通过分析STL源码解析文档的第二章—空间配置器。帮你打开STL源码的大门,也让你理解内存配置器,大家都叫它内存池。
先说说STL空间配置器的总体概念吧:先分配一个大的内存空间(malloc),然后把此块内存分成更小的一块一块,程序需要内存空间时,不再是使用new或malloc,而是直接从内存配置器中拿取一块空闲的内存块,程序释放内存时,是把内存块放回内存配置器。整个过程只使用一次malloc分配,之后的所有操作都是在这大块内存中进行。这样便更高效,也能减少内存碎片。其实这就是内存池的思想。后面我会为大家分析一个内存池的实现。只要你明白了这个原理,往下看就简单点了。首先你得下载一个高清的有目录的STL源码文档。
地址:http://download.youkuaiyun.com/detail/wulang150/8122917
从这里开始,让你能更好的理解STL空间配置器是怎样使用上面的原理想法的。STL采用两级配置器,第一级配置器就是直接向操作系统申请内存,看源码,可以看到使用的是malloc,没骗你吧!
第二级配置器就深深地体现了上面所说的内存池原理,先分配一块大内存,再把此块内存分成一小块一小块。看图。
上图他妈画得太好了。是不是看到那大块内存被分成了许多小块,并且它们用链表组织起来,从链表头取,用完又放回链表头。STL维护了数个这样的链表,基本原理不变。这里说说,第二级配置器的那块大内存是通过一个函数获得,其实也是调用malloc,只是它在提供给free_list内存的时候,多做了很多处理,STL也叫它为内存池。说白了,第二级配置器就是链表+内存池的组合,程序直接从链表头拿内存,链表没内存了,便想内存池申请,内存池没内存了就调用malloc。
读到这里,你应该比较清楚了解内存配置器的原理了吧。好吧,现在看看STL提供的内存配置函数接口。它提供什么接口让我们向它申请内存,又用什么接口给我们释放呢?
写得太明显了,allocate()是申请内存,deallocate()是释放内存。似乎在向你呼叫,以后要用内存,别再用new或malloc了,用我吧—allocate()。
Allocate()部分内部的实现:
可以看到,分配的内存大于128就调用第一级配置器,其他都是调用二级,大家有没看到free list的身影。释放的我就不说了,自己去看。你别以为看了这篇文章就懂了空间配置器,必须的自己去看,去体会,文章只是个引导,让你下次看STL源码不再卡在第二章。
为了让大家进一步理解内存配置器,下面就为大家提供一段内存池的代码,你可以拿代码去调用,也可以拿来使用。我就经常在公司项目中使用。效果刚刚的,其中的Pool.h的内存池是我经常使用,里面有注释,你看得懂的。具体怎么用?简单说说吧
下载:http://download.youkuaiyun.com/detail/wulang150/8123733
先#include “Pool.h”
Pool myMem;
//Node *s = new Node(); //注释的代码,原本要使用new的,后来改为内存池提供
Node *s = (Node *) myMem.Alloc(); //申请
memset(s,0,sizeof(Node));
new(s) Node(); //调用对象的构造函数
p->~Node(); //调用析构函数
myMem.Free(p); //释放
申请到内存后,记得要调用构造函数哈,释放内存前记得要调用析构函数。这些东西在STL源码也有说明:
构造使用construct,析构使用destroy,这两个东西,到你看vector源码便会发现。