STL库的内存配置器(allocator)


正在学习中,如果有错,还请多多指教,根据不断的理解,会进行更改,更改之前的样子都会保留下来,记录错误是最大的进步,嗯嗯!

STL源码剖析简体中文完整版(高清晰扫描带目录)PDF 下载地址 

具有次配置力的SGI空间配置器(SGI是STL的一种版本,也有其他的版本)

这里我就不贴出来具体成员和接口的实现了,网上可以搜到STL的源码

C++中,new一个变量可以分为两个阶段,1.分配空间 2.调用构造函数;delete变量也分为两个步骤,1.调用析构函数 2.释放空间

SGI的alloc将这两部分分开,让空间的分配释放和构造析构由不同的函数调用,区分他们的操作

空间的分配和释放由alloc::alloclate()  alloc::dealloclate()实现 构造和析构由alloc::construct() alloc::destory()函数负责,他们在头文件

#include<stl_alloc.h> //负责内存空间的配置和释放 2 #include<stl_construct.h> //负责对象内容的构造和析构 这两个头文件都包含在#include<memory>当中

先讲一下析构和构造的大概思路  

    • 这里提到一个概念_type_traits<T>(这是个模板),首先通过value_type()获得迭代器所指向对象的类型然后使用_type_traits进行类型判别,该对象是否是trivial(无关痛痒的) destructor,我对这里的理解是,当迭代器所指对象的类型为POD(Plain Old Data)类型(PS:POD类型可以理解为传统的C语言类型,就是int那一类的)是不需要专门调用析构函数的,等待系统自动释放int所占用的空间即可,但当迭代器所指对象的类型为string对象(或者是其他你自己写的类的对象),就不能依靠系统,需要调用专门的析构函数挨个析构。进行类型判别之后,就可以提高工作效率。(destory()对char*等类型也有相应的特化)
    • 构造函数就采用泛式构造,使用new进行构造
  • 下面讲一下我对空间的配置与释放的理解
    • C++对内存配置的基本操作依靠::operator new()和::operator delete()这两个全局函数,这两个函数只起到分配和释放内存的作用,并不会调用构造函数和析构函数,他们就相当于C语言中的malloc()和free()函数,SGI正是以malloc()和free()进行空间的管理
    • 为了解决内存碎片的问题(当不断地malloc空间时候,找到足够大小的空间就拿来用,不可避免的的会产生内存碎片),于是就有了内存池的概念(内存池就是系统实现给开辟出一大块空间等待使用,当需要空间的时候直接从内存池中取,当内存池中的内存也不够使用时,再去Heap中开辟新的空间注入到内存池当中)

下面就比较重要了,SGI空间配置器采用了两级配置器,分为第一级配置器和第二级配置器,第一级配置器就是拿malloc()实现的,第二级配置器采用了内存池的概念;

先来讲第一级配置器,第一级配置器的实现就是用的malloc()(因为很重要所以多说几遍),它会不断地尝试开辟内存,如果没有内存可供开辟,就会尝试释放空间,然后再取尝试开辟空间,第一级配置器比较重要的一点就是实现了类似C++中new-handler机制,用来处理内存不足的情况

内存池是使用链表结构实现的(个人感觉和哈希桶的方法类似),而不采用顺序表,这样就可以更好的解决内存碎片的问题了。

注意,这里的freelists是链表!

注意!这里之前写的不太清楚,freelists是一个顺序表,每一个单元下边都连接这一个链表,当区块被客端(client)使用就会像上图一样将区块拨出去,然后改变指针指向下一个节点(就是采用头删)

每个链表节点所管理的区块大小也是不一样的,第一个节点管理8bytes,第二个16bytes。。。以此类推,最后一个节点管理128bytes,所以当超过128字节第二级配置器无法处理,就只好调用第一级配置器(内存不足时也会调用第一级配置器,因为第一级配置器里面有内存不足处理例程)

下面给出链表节点的实现

1 union obj
2 {
3     union obj *free_list_link;
4     char client_data[1];//The client sees this;
5 };   

里面的联合体指针就是指向下一个节点的指针,那个char是指向实际内存块的指针,采用联合体可以减少内存的消耗,不必专门维护一个指向下一个节点的指针

当节点所指的内存块是空闲块时,obj被看做一个指针,指向下一个节点,当节点已经被分配了内存之后,被视为一个指针,指向实际区块

当分配函数allocate()(alloc中申请内存的函数)发现没有可用的区块之后,就会去内存池中申请新的区块(调用chunk_alloc()函数) ,默认申请20个区块,当内存池的可用内存不足20个区块,有多少给多少,如果连一个区块的内存都不够,就往内存池中注入内存(就是再去开辟一些放进内存池里),就会去free_lists中遍历,寻找内存池分配给自由链表但是却处于空闲状态的节点,将这些节点的存储空间归还个内存池,再递归去使用chunk_alloc()函数判断是否空间够分配;如果很不幸,还是不够用,那么注意!!会将内存池中剩余的小空间分配成低位区块分给自由链表(就是说假如需要申请16个字节的节点区块,不够了,但是内存池中有8个字节的空间,当然不能够浪费咯,赶紧分配出去)然后去往内存池中注入内存(就是再去开辟一些放进内存池里)。

这个是《STL源码剖析》里的一个例子

当整个系统堆得内存都不够了的时候,就chunk_malloc()就会四处寻找可用内存,尝试释放一些没用的空间,如果还是无法找到就去调用第一级配置器,因为一级配置器里有内存不足处理例程

本文永久更新链接地址

相关内容