• STL库的内存配置器(allocator)


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

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

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

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

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

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

     1 #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()就会四处寻找可用内存,尝试释放一些没用的空间,如果还是无法找到就去调用第一级配置器,因为一级配置器里有内存不足处理例程

  • 相关阅读:
    JavaScript基础知识-标识符
    free命令常用参数详解及常用内存工具介绍
    GO语言的进阶之路-初探GO语言
    HTML&CSS基础-字体的其它样式
    HTML&CSS基础-字体的分类
    HTML&CSS基础-字体的样式
    python运维常用相关模块
    HTML&CSS基础-颜色的单位表示方法
    HTML&CSS基础-长度单位
    HTML&CSS基础-定义列表
  • 原文地址:https://www.cnblogs.com/lenomirei/p/5357970.html
Copyright © 2020-2023  润新知