最近在阅读《STL源码剖析》这本书,会将自己觉得比较有意思的东西写在博客中
这本书中最先介绍的就是空间配置器的使用,在SGI STL中,分为两级的空间配置器,下面依次介绍这两种空间配置器是如何进行STL的内存管理的
配置器的总体介绍
SGI STL使用的是malloc()和free()函数来进行内存分配和释放的,考虑到小型区块可能造成的内存破碎的问题,SGI设计了双层的空间配置器。第一级配置器直接使用malloc()和free(),比较简单,第二级配置器则采用了内存池的技术,来减少内存的破碎。
当然没看过这本书的人可能会疑惑,到底什么是空间配置器?这里可以稍微下个定义,你可以认为空间配置器(allocator)是每个容器底层的一个对象,里面包含了一堆函数,用于给容器来分配内存空间。
第一级空间配置器
第一级空间配置器较为简单,使用malloc(),free(),realloc()等C函数执行实际的内存配置、释放、重配置操作
第二级空间配置器
第二级配置器多了一些机制,用于避免太多小额区块造成的内存碎片,由于一块内存中包含有两部分:用于记录内存大小的cookie、内存,如果申请的内存过小,那么这个cookie相对来说占的比重会更大,从而造成不必要的浪费。
SGI第二级配置器的做法是,如果申请的内存区块够大(超过128字节),就直接移交给第一级配置器来处理,如果区块小于128字节,就用内存池(memory pool)来进行管理。
第二级配置器在申请内存的时候,会直接申请一大块内存,并用十六个自由链表(free-list)来维护这一块内存。下次需要申请内存的时候,就直接冲free-lists中挑出一块内存来使用,如果客端想要归还内存,配置器就会将这块内存回收到free-lists之中。
为了方便管理,SGI第二级配置器会将任何小额区块的内存上调至8的倍数,维护16个free-list,大小分别为8、16、24、32、40、48、56、64、72、80、88、96、104、112、120、128字节。free-lists的节点结构如下:
1 2 3 4
| union obj{ union obj * free_list_link; char client_data[1]; };
|
关于free-lists的解释
节点是union的结构,结构中free_list_link和client_data[1]公用一块内存,这是为了节省内存,当该节点的内存并没有被使用的时候,结构中存储的是指向下一个节点的地址,当该节点的内存被使用的时候,存储的即是内存的地址。这样就不用在节点中开两个地址的变量,从而节约了内存。
关于空间配置函数allocate()
在网上找到的关于这个函数的解释都十分的不清楚,这里给出代码的合理解释
首先需要注意的是,有许多个8个字节大小的内存空间,所有的8字节大小的内存空间都由一个链表管理,之后的许多个16个字节大小的内存空间、24个字节大小的内存空间也和这个类似,每种大小的内存空间都由一个链表来进行管理,所以说一共有16个free-list。
以96个字节大小的内存空间为例子,二级空间配置器中由一个数组free_list[16],这个数组的每一个元素,存储一个内存空间的地址,这个地址指向的就是96字节大小的free-list的第一个结点,而这个节点世界上就是一个内存空间,只是这个内存空间的前面四个字节,存储的是指向下一个节点的地址。如果要取出这块内存空间,需要将free_list[11]这个元素更新成下一个节点的地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| static void * allocate(size_t n) { obj * __VOLATILE * my_free_list; obj * __RESTRICT result; if (n > (size_t) __MAX_BYTES) { return (malloc_alloc::allocate(n)); }
my_free_list = free_list + FREELIST_INDEX(n);
result = *my_free_list; if (result == 0) { void *r = refill(ROUND_UP(n)); return r; }
*my_free_list = result -> free_list_link; return (result); };
|