从STL实现的角度来看,第一个需要介绍的就是Allocator空间配置器,因为整个STL的操作对象(所有的数值)都存放在容器之内,而容器一定需要配置空间以置放资料。
allocator是空间配置器,空间不一定是内存,可以是磁盘或其他辅助存储介质。SGI STL提供的配置器,配置的对象是内存。
一般来说,我们所习惯的C++内存配置操作和释放操作是这样的:
其中,new算式内含两阶段操作:①调用::operatornew配置内存;②调用Foo::Foo()构造对象内容。同样delete算式也内含两阶段操作:①调用Foo::~Foo()将对象析构;②调用::operatordelete释放内存。
STL标准规格告诉我们,配置器定义于<memory>之中,SGI<memory>内含两个文件:
#include <stl_alloc.h> |
|
|
| //负责内存空间的配置与释放 |
#include <stl_construct.h> |
|
| // 负责对象内容的构造与析构 |
对于对象的构造和析构,是由construct()和destory()两个函数完成的。
而对象构造前的空间配置和对象析构后额空间释放,SGI对此的设计哲学如下:
①向 systemheap要求空间
②考虑多线程状态
③考虑内存不足的应变措施
④考虑过多“小型区块”可能造成的内存碎片(fragment)问题。
C++的内存配置基本操作是::operator new(), 释放 ::operatordelete(),这两个全局函数相当于C中的malloc(), free().而SGI正是以malloc()和free()完成内存配置和释放的。
考虑到小型区块所可能造成的内存破碎问题,SGI设计了双层级配置器。第一层配置器直接使用malloc() free(),第二级配置器则是情况不同采用不同的策略。当配置区块超过128bytes时,视之为足够大,便调用第一级配置器;当内存区块小于128bytes,视之为“过小”,为了降低额外负担(overhead), 便采用复杂的memorypool整理方式(内存池)。
第一级配置器:__malloc_alloc_template
所以,重配置操作需要客端指定。
第二级配置器:__default_alloc_tempalte
第二级配置器多了一些机制,避免太多小额区块造成内存的碎片,以及配置时的额外负担(overhead).
申请的内存空间越小,额外负担所占的比例越大,浪费也就显得越多。
解决方法——内存池管理(memorypool)或者次层配置(sub-allocation)
当区块小于128bytes时,每次配置一块大内存,并维护对应的自由链表(free-list).下次若再有相同大小的内存需求,
就直接从free-lists中拔出。如果客端释还了小额区块,就由配置器回收到free-list中。
为了方便管理,SGI第二级配置器会主动将任何小区快的内存需求量调至8的倍数,并维护16个free-lists,各自管理大小
分别为8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128bytes的小额区块。128=8*16.
Free-lists的节点结构如下:
使用union是为了减小额外负担。以第一个字段观之,obj可被视为一个指针,指向相同形式的下一个object
从第二个字段观之,object可被视为一个指针,指向实际区块。
一物二用的结果是,不会为了维护链表所必须的指针而造成内存的另一种浪费。
这个结构可以看做是从一个内存块中抠出4个字节大小来,当这个内存块空闲时,它存储了下个空闲块,当这个内存块
交付给用户时,它存储的时用户的数据。因此,allocator中的空闲块链表可以表示成:
obj*free_list[16];
free_list[i]指向的是8*(i+1)bytes的内存块的头地址。
下图所示为空间配置和空间回收过程。