引言:
由于整个STL的操作对象都存放在容器之内,而容器一定需要配置空间来放置资料,所以引入了空间配置器。
思考:为什么要有空间配置器,而不直接在使用时申请空间?
1. 内存空间碎片化问题
一般在申请空间时,系统将分配足够使用的一段连续空间,由于频繁分配、释放尤其是小块内存,就更容易造成空间碎片的问题(危害:即使内存空间刚好满足一个请求,但由于内存空间分散,导致任意单独的内存块都不能被分配,从而造成内存利用率低)。而空间配置器是通过内存池的方式去管理,尤其适合对小块内存的管理。
2. 性能问题
我们能每次在申请空间时,分配器都要去找一块空闲块给用户,延伸上一问题如果空间碎片较多,那势必分配器要花更多时间去匹配合适的空闲块,假如差的情况下没有单独合适大小的空闲块,就要考虑合并哪些小的空闲块,而这也需要花费时间处理。
另外,malloc在开辟空间时,要对这些内存块添加额外信息,如果频繁申请小块内存,这也会造成较大的负担。
空间配置器:
注:这里所述皆为SIG-STL的空间配置器。
空间配置器的构成:
-
构造与析构
这里的构造与析构函数属于STL标准规范。
原理:
-
空间的配置与释放
之前已经介绍过alloc可以是第一级空间配置器或是第二级空间配置器,但在SGI-STL下默认为第二级空间配置器。 除此之外,为alloc封装了一个接口(simple_alloc),在调用时就可以使用此接口,而不论alloc是哪一级都以模板参数方式传给simple_alloc(顺便提醒:alloc不接受任何template型别参数)。
定义alloc:
// 将alloc定义为第一级配置器
typedef __malloc_alloc_template<0> malloc_alloc;
typedef malloc_alloc alloc;
// 将alloc定义为第二级配置器
typedef __default_alloc_template<0,0> alloc;
接口封装定义如下:
template<class T, class Alloc>
class simple_alloc{
public:
static T* allocate(size_t n)
{return 0==n ? 0 : (T*) Alloc::allocate(n*sizeof(T));}
static T* allocate(void)
{return (T*) Alloc::allocate(sizeof(T));}
static void deallocate(T* p, size_t n)
{if(0 != n) Alloc::deallocate(p, n*sizeof(T));}
static void deallocate(T* p)
{Alloc::deallocate(p, sizeof(T));}
}
第一级空间配置器:
当第二级配置器没有足够空间时,就会求助第一级空间配置器。第一级空间配置器的处理流程:
其使用完的直接通过free释放。
第二级配置器:
之前说过第一级配置器是在第二级配置器空间不足时调用,而第二级配置器的最大能力是128字节。
我们知道第二级配置器是用内存池进行管理的,其管理方式也叫做次层配置:用自由链表来管理一块的内存。如果有大小相同的内存需求则直接从链表中取出,当使用结束后,配置器将释放的空间还回链表。
配置器会将每次请求的内存升至为8的倍数。虽然这样做依然会造成部分空间浪费,但却大大方便了我们的管理,因为只需要维护16个free-list就可以了。另外,为了节省空间,链表节点被设置为union:
union obj{
union obj* free_list_link; // 指向自由链表节点的指针
char client_data[1]; // 指向实际区块
}
配置器是通过free_list[16]这样的指针数组维护这16个自由链表的:
数组每个元素代表一个指针指向相应链表的地址:
这里有一疑问,obj第二成员为什么是char client_data[1]?
首先理解union类型的特性:
- union类型大小是其成员中最大的类型大小;
- 成员共用同一块内存空间而且可作为成员中的任意一种类型来使用;
- union的存放顺序是所有成员都从低地址开始存放。
在就我个人理解:obj应该是4字节大小,对于free_list_link而言存放的是下一节点地址。而对client_data[1],它的作用是能通过其数组首地址来确定obj或是说此区块的起始位置的。
第二级空间配置器处理流程:
由于第二级空间配置情况较多在此分情况讨论:
- 在链表中刚好有所要请求大小(经过上调至8倍数,之后不在赘述)的内存块:
采用类似头删的方式取出内存块。
- 与请求块大小相等的区块链表为空时:
链表没有可用块时,向内存中申请填充:
- 相应链表和内存池都为空时:
- 堆区也没内存:
空间配置器缺点:
1. 空间配置器所有的函数和变量都是静态的,所以只有程序结束时才会释放,导致自由链表上的内存块只能自己进程使用,其他进程用不了;
2. 第二级配置器依然会产生内存碎片,造成内存浪费;
3. 第一级配置器中,若客端对内存不足处理方式不当,可能造成死循环。