zoukankan      html  css  js  c++  java
  • STL内存管理

      过年在家无事看了《STL源码剖析》,开学了将看过的东西总结一下,以防忘记。

      先从STL的内存管理开始总结。掌管STL内存控制的是一个叫空间适配器(alloc)的东西。STL有两个空间适配器,SGI标准空间适配器(allocate)和SGI特殊的空间适配器(alloc),前者只是对c++的new和delete进行了简单的封装。下面主要介绍的是alloc空间适配器。

      一般而言,c++的内存配置和释放操作是这样的:

    class Foo {...}
    
    Foo* pf = new Foo;
    
    delete pf;

      其中new操作符包含了两个阶段:1、operator new配置空间;2、调用Foo构造函数构造内容。同理,delete操作符也包含调用析构函数和释放空间两个过程。STL内存管理参考了这种方法,将空间配置和构造内容分开。内存配置和内存释放分别由alloc::allocate() 和 alloc::deallocate() 负责,对象构造和析构分别由alloc::construct() 和 alloc::destroy()负责。

      下面先介绍construct() 和 destroy() 函数。

    template <class T1, class T2>
    inline void construct(T1* p, const T2& value){
      new (p) T1(value); // 这里的new是placement new,是operator new 的重载版本,只调用对象的构造函数,并不进行内存的分配. operator new如上所说,会有两步。   
    }
    template <class T>
    inline void destroy(T* pointer){
        pointer->~T(); // 调用析构函数
    }

      从代码中可以看出,负责对象构造和析构的alloc::construct() 和 alloc::destroy()函数就是对构造函数T()和析构函数~T()的封装。(注意:上面代码并没有涉及任何的内存配置与释放,单纯的调用构造函数和析构函数,这就是constructor()里面为什么用placement new 而不是 operator new 的原因。)另外,alloc重载第二个版本的alloc::destroy(),接受两个iterator参数,用来批量地析构对象,并且用type_traits编程技法判断对象是否有tirvial destructor来判断是否需要显示的调用destroy函数,以此来加快批量析构对象的速度。其中type_traits简言之就是用泛化的手法提取对象的型别信息,用来判断该对象到底有没有trivial destructor,trivial destructor现在你可以理解为没有显示地定义析构函数,例如系统内置的int型变量,后面等有时间再总结一下type_traits和iterator_traits的实现原理。

      前面总结了内存配置的第二部分----对象的构造和析构,下面总结一下第一部分----空间的配置与释放。

      SGI以malloc()和free()完成内存配置与释放。为了解决小型区块所可能造成的内存破碎问题,SGI设计了双层级配置器,第一级直接使用malloc()和free()配置和释放内存空间,第二级配置器采用如下策略:当配置区块超过128 bytes 时,视为足够大,调用第一级配置器,当小于128 bytes 时,采用memory pool的整理方式。下面主要总结第二级配置器。

      如前所述,SGI第二级配置器的策略为:如果区块足够大,超过128 bytes 时,就移交第一级配置器处理。当区块小于128 bytes 时,则以memory pool管理----每次配置一大块内存的时候,都对应维护一个自由链表(free-list)。下次若有相同大小的内存需求时,直接从free-list中取,当有内存被释放时,回收到free-list当中。第二级配置器维护16个free-list,分别管理8,16,24...128 bytes 大小的区块,客户端请求的区块会自动调整到上述大小,如 20 bytes 调整到 24 bytes。下面是free-list的节点。

    union obj{
        union obj* free_list_link;
        char client_data[1];  // 客户端看到的是这个字段    
    }

    这里之所以用union而不同struct,是为了节约空间,union中的变量共用同一个存储空间,obj里面就存储着一个地址,指向下一个可用区块,而那个区块的首个字节就是obj,也可以说这个地址也指向下一个obj。可以这么理解:可用区块的第一个字节构成了一个链表,维护了同样大小的区块,而第一个区块的地址就放在free-list中。当free-list里面某个大小的区块无可用区块时,就由chunk_alloc() 函数向内存池请求空间。上述总结可能没有那么直观,下面结合部分代码具体解释。由于代码较多,故只挑选关键代码附以自己的理解,全部代码可参考《STL源码剖析》。注:源码中使用union的做法只是为了节约空间,但是现在内存大小已不是人们关心的主要问题,又加之union在实际使用中会出现各种奇怪问题,所以大部分情况下还是推荐有struct代替。

    // __NFREELISTS == 16 表示配置器所维护的16个free_list; volatile表示不允许编译器对free_list变量进行优化; free_list里面放的是obj的地址,也就是说该大小下可使用的第一个区块。
    static obj * volatile free_list[__NFREELISTS];
    static void * allocate(size_t n){
      obj * volatile * my_free_list; // 是指向obj*的指针
      obj * result;
      // 如果大于128就调用第一级适配器
      if(n > (size_t) __MAX_BYTES){
          return(malloc_alloc::allocate(n));
      }
      // 找到16个当中合适的free_list
      my_free_list = free_list + FREELIST_INDEX(n);
      result  = * my_free_list;
      if(result == 0){
        //如果没有找到可用的free_list,准备重新填充free_list
        void *r = refill(ROUND_UP(n));
        return r;
      }
      *my_free_list = result -> free_list_link;// 这里是理解的关键,其实result已经指向了第一个可用区块,区块的第一个字节是下一个可用区块也是下一个obj的地址,所以这句话就已经移除了第一个可用区块。
      return result;
    }

      空间释放函数deallocate()首先判断是否大于128 bytes,大于交由第一级配置器处理,小于就找出对应的free_list将之收回。在理解了allocate()的代码之后,deallocate()里面的关键代码也就不难理解了:

    my_free_list = free_list + FREELIST_INDEX(n);
    q -> free_list_link = *my_free_list; // q指向要收回的区块
    *my_free_list = q;

      重新填充 free_list 的 refill() 函数就是用chunk_alloc函数从内存池中取出新的空间(缺省为20个某个大小的新区块,如内存池空间不够也可能小于20),并构造成链表的形式挂在当前大小的free_list后面。在理解了新区块的第一个字节为下一区块以及下一obj的地址这一概念之后,refill()的代码也不难理解,故不在下面贴代码了。

      前面一直提到的chunk_alloc()函数,它的工作是为free_list从内存池中取空间。它的逻辑为若内存池空间完全满足需求,则为free_list配置20个新区块,若内存池剩余空间不能完全满足需求,但足够供应一个及以上的区块,则为free_list配置力所能及的区块数目,若内存池剩余空间连一个区块大小都无法满足,就先把内存池中的剩余零头分配给适当的free_list,然后释放维护更大区块的free_list中的取尚未用的区块,将空间还给内存池,以供内存池为当前free_list配置空间,若,山穷水尽,到处都没有内存了,那就只能求助于第一级配置器,如果他再搞不定,那就只能抛出异常了。在理解前面代码的基础上,chunk_alloc的代码也不难理解。

      本章后面还介绍了五个STL的全局函数,作用于未初始化的空间上,方便后面的容器实现。

      STL的内存管理大体就是这样,主要用了malloc 和 free对内存进行配置,等回头看看TCMalloc对malloc 和 free 改进再总结一记吧。

  • 相关阅读:
    分层图最短路(DP思想) BZOJ2662 [BeiJing wc2012]冻结
    动态规划 BZOJ1925 地精部落
    线性DP SPOJ Mobile Service
    线性DP codevs2185 最长公共上升子序列
    数位DP POJ3208 Apocalypse Someday
    线性DP POJ3666 Making the Grade
    杨氏矩阵 线性DP? POJ2279 Mr.Young's Picture Permutations
    tarjan强连通分量 洛谷P1262 间谍网络
    树链剖分 BZOJ3589 动态树
    二分图 BZOJ4554 [Tjoi2016&Heoi2016]游戏
  • 原文地址:https://www.cnblogs.com/zpjjtzzq/p/4540545.html
Copyright © 2011-2022 走看看