zoukankan      html  css  js  c++  java
  • SGI STL内存配置器(一):内存泄漏?

    阅读了Alexander大神的SGI STL源码,膜拜,很高质量的源码,获益匪浅。温故而知新!下文中所有STL如无特殊说明均指SGI版本实现。

    STL 内存配置器

    STL对内存管理最核心部分我觉得是它将C++对象创建过程分解为构造、析构和内存分配、释放!摆脱了由于频繁调用new或malloc函数向操作系统申请空间而造成的低效。其中析构操作时对具有non-trival、trival 析构函数的class区别对待也提高了效率。至于SGI的两级配置器结构则属于锦上添花的类型。

    STL两级结构的内存配置器使得STL能对小的空间内存分配管理更为合理,其二级结构表现为该配置器在获取内存和释放内存时分为两种情况,对大于128Byte的内存块直接调用malloc,不大于128Byte的内存块使用一个内存池和一个链表cache来单独维护。接下来结合代码中的注释,概述一下这个二级结构的工作机制!

    该内存配置器实现在类__default_alloc_template 中 [源码1]

    template <bool threads, int inst>
    class __default_alloc_template {
    ...
    };
    

     

    其中内存池相关变量代码:

        // Chunk allocation state.
        static char* _S_start_free; //内存池起点
        static char* _S_end_free;  //内存池终点
        static size_t _S_heap_size;//共向操作系统申请过的多少空间补充给内存池
    

    用来维护从内存池中取出来返回给过客户端代码的小内存块的链表:

    # if defined(__SUNPRO_CC) || defined(__GNUC__) || defined(__HP_aCC)
        static _Obj* __STL_VOLATILE _S_free_list[]; 
            // Specifying a size results in duplicate def for 4.1
    # else
        static _Obj* __STL_VOLATILE _S_free_list[_NFREELISTS];//数组的每个元素的值为一串相同size的内存块链表头,其中 _NFREELISTS = 16, 内存块size取值为8,16,..., 8*16
    # endif

    而在__default_alloc_template 中对内存获取和释放主要通过两个静态成员函数来完成:

      获取 allocate(size_t __n) :

    static void* allocate(size_t __n)
      {
        void* __ret = 0;
    
        if (__n > (size_t) _MAX_BYTES) {//大于128Byte
          __ret = malloc_alloc::allocate(__n);//这里其实是对malloc的包装
        }
        else {
          _Obj* __STL_VOLATILE* __my_free_list
              = _S_free_list + _S_freelist_index(__n);//从链表维护的cache中取出满足条件的内存块
          // Acquire the lock here with a constructor call.
          // This ensures that it is released in exit or during stack
          // unwinding.
    #     ifndef _NOTHREADS
          /*REFERENCED*/
          _Lock __lock_instance;
    #     endif
          _Obj* __RESTRICT __result = *__my_free_list;
          if (__result == 0)//如果取出失败,从内存池中获取内存来填充Cache
            __ret = _S_refill(_S_round_up(__n));
          else {//取出成功则直接返回
            *__my_free_list = __result -> _M_free_list_link;
            __ret = __result;
          }
        }
    
        return __ret;
      };

      释放 deallocate(void* __p, size_t __n):

     /* __p may not be 0 */
      static void deallocate(void* __p, size_t __n)
      {
        if (__n > (size_t) _MAX_BYTES)//大于128Byte直接调用free释放
          malloc_alloc::deallocate(__p, __n);
        else {//释放回链表组织的Cache中
          _Obj* __STL_VOLATILE*  __my_free_list
              = _S_free_list + _S_freelist_index(__n);
          _Obj* __q = (_Obj*)__p;
    
          // acquire lock
    #       ifndef _NOTHREADS
          /*REFERENCED*/
          _Lock __lock_instance;
    #       endif /* _NOTHREADS */
          __q -> _M_free_list_link = *__my_free_list;
          *__my_free_list = __q;
          // lock is released here
        }
      }

    STL内存配置器有没有内存泄漏?

    看了源码后很多人疑惑为什么在该Allocator的实现里只有对内存池malloc的代码,没看到类似free这样释放内存的代码,甚至该Allocator类都没有析构函数,这样不是会存在内存泄漏吗?

    其实不然。对于由链表维护的内存,其内存的释放工作应该是上一层调用者负责,比如容器Vector在析构函数中就将其申请的所有capacity大小的内存释放。相反内存池的中的内存将会一直保留直到程序退出。有的同学可能会认为“这不就是内存泄漏吗?比如创建了一个Vector变量,到Vector析构了之后再内存中竟然有一块内存没有被系统回收,这不是memory leak吗”。其实不然:

    1. 申请的内存没有被及时释放 不等于 内存泄漏

    在单线程中,由于该Allocator中记录内存池起始的指针是静态类型,所以只要是你在同一个线程中,无论你创建多少个Allocator,记录内存池的变量都是同一个,换句话说,当下次再创建Vector时,还是使用上一次使用的那个。也就是说他的存在时有意义的,这也是cache或memory pool的意义所在!

    2. 该内存池不会疯狂野生长

    这个内存池的空间其实是很小的,因为大于128Byte的内存申请都直接转调了malloc,从源码中也可以看出,内存池每次重新灌水的新容量是2*total_size + round_up(heap_size >> 4)。

    内存池的存在是为了避免大量内存碎片的产生,代价是管理内存所需要多付出的时间和空间消耗。

    以上就是内存池一种存在直至程序退出的原因。

    在GCC 5.4.0 中的使用的SGI已经对该种设计做了大幅修改:

    1. 默认的Allocator也不在是侯捷一书中说指出的具有内存池的配置器,而是"usrincludec++5ext ew_allocator.h"其实现直接调用new;

    2. 而之前相应的具备内存池的配置器则被当做STL的扩展,实现于"usrincludec++5extpool_allocator.h"中,且该实现不在存在内存池的设计,只保留了使用链表将小内存块连接起来的设计(使用时记得include该文件路径,命名空间为__gnu_cxx::__pool_alloc<int> )。

    而在llvm的实现中Allocator也是直接调用的new函数。

    FYI:

    GCC更换Allocator设计,http://www.cppblog.com/peakflys/archive/2015/01/14/209513.html

    SGI源码,https://www.sgi.com/tech/stl/download.html

    SGI源码,allocator,https://www.sgi.com/tech/stl/stl_alloc.h

    SGI源码,vector,https://www.sgi.com/tech/stl/stl_vector.h

  • 相关阅读:
    学习Javascript闭包(Closure)
    JS的this原理
    页面锚点的设置
    JS异常捕获和抛出
    C++ 指针初始化要注意的地方
    Jupyter Notebook里面使用Matplotlib画图 图表中文乱码问题
    Matplotlib 基本图表的绘制
    Matplotlib 子图的创建
    Matplotlib 图表的样式参数
    Matplotlib 图表的基本参数设置
  • 原文地址:https://www.cnblogs.com/pray/p/7533630.html
Copyright © 2011-2022 走看看