STL-ch2-空间配置器

STL空间配置器

空间配置器——《STL源码剖析》chapter2

2.1 STL标准规定的空间配置器的标准接口

  • STL规定的空间配置器的名称是allocator
  • 包含下列typedef:
    • value_type
    • pointer/const_pointer
    • reference/const_reference
    • size_type
    • difference_type
  • 包含allocator::rebind
  • 包含成员函数:allocate(), deallocate(), construct(), destroy()。这4个全是成员函数,作用分别是:配置空间,释放配置空间,构造对象,析构对象。
  • SGI实现的STL未遵守STL标准的规定,使用的是一个具有次配置能力(sub-allocation)的特殊配置器。

2.2 具有次配置能力的SGI STL空间配置器

2.2.1 SGI也实现了STL标准规定的std::allocator

但是不建议用(只是对operator newoperator delete做了简单封装)

2.2.2 SGI中的特殊配置器名字叫std::alloc

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

    上面的new算式包含两阶段操作:1. 调用operator new配置内存,2. 调用Foo::Foo()构造函数对象。

    delete算式也包含两阶段操作:1. 调用Foo::~Foo()将对象析构,2. 调用operator delete释放内存。

  • 为了精密分工,STL配置器将这两阶段操作区分开来。内存的配置和释放由alloc成员函数alloc::allocate()alloc::deallocate()来负责,对象的构造和析构由全局函数::construct()::destroy()来负责。

  • 标准规定,配置器定义于头文件<memory>中,<memory>包含两个文件:#include<stl_alloc.h>(这里面定义了一二级配置器)和#include<stl_construct.h>(这里定义了全局的::construct()::destroy())。

2.2.3 构造和析构基本工具::construct()::destroy()

  • ::construct():利用placement new在传入的地址上构造对象。
  • ::destroy():有多个版本。1. 接受一个指针类型:直接调用指针指向对象的析构函数;2. 接受两个迭代器,需要先判断类型的析构函数是否trival(无关紧要),如果trival就什么都不做,如果不trival就遍历,依次调用上一个版本的destroy()

2.2.4 空间的配置与释放std::alloc

  • 考虑小区块造成的内存破碎问题,SGI设计了双层级配置器。
  • 第一级:直接使用malloc()free()
  • 第二级:
    • 如果配置区块大小超过128byte,直接用malloc()free()
    • 如果配置区块大小不足128byte,为降低额外负担,采用复杂的memory pool整理方式。

2.2.5 第一级配置器malloc_alloc

  • template<int inst>
    class __malloc_alloc_template__{...};
    //别名
    typedef __malloc_alloc_template__<0> malloc_alloc;
    
  • allocate()直接使用malloc()

  • deallocate()直接使用free()

  • 模拟C++的set_new_handler()以处理内存不足的状况。

2.2.6 第二级配置器

  • sub-allocation:每次配置一大块内存,并维护对应之自由链表(free-list)。下次若再有相同大小的内存需求,就直接从free-lists中拔出。如果客端释还小额区块,就由配置器回收到free-list中。

  • 第二级配置器会自动将内存需求量上调至8的倍数,并维护16个free-lists(分别管理8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128bytes的小额区块)

  • free-list节点的结构

    union obj{
        union obj * free_list_link;
        char client_data[1];
    };
    

    使用union,一物二用,每个节点不需要额外的指针来维护链表。

2.2.7 空间配置函数allocate()

  • 源码:

    // n must be > 0
    static void * allocate(size_t n)
    {
        obj * volatile * my_free_list;
        obj * result;
        // 大于128就调用第一级配置器
        if(n>(size_t)__MAX_BYTES){
            return (malloc_alloc::allocate(n));
        }
        
        //寻找16个free-lists中适当的一个
        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;
        }
        
        //调整free-list
        *my_free_list = result->free_list_link;
        return (result);
    }
    
  • 第一步:找打n对应的free-list;第二步:将result指向这个free-list的开头;第三步:调整free-list。二三两步就是将一个区块从free-list中摘下来。

  • 如果没有可用的free-list,调用refill()重新填充free-list

2.2.8 空间释放函数deallocate()

  • 源码:

    //p 不可以是0
    static void deallocate(void *p, size_t n)
    {
        obj *q = (obj*)p;
        obj *volatile * my_free_list;
        
        // 大于128就调用第一级配置器
        if (n>(size_t)__MAX_BYTES){
            malloc_alloc::deallocate(p,n);
            return;
        }
        
        my_free_list = free_list + FREELIST_INDEX(n);
        //调整free-list,回收区块
        q->free_list_link = *my_free_list;
        *my_free_list = q;
    }
    

2.2.9 重新填充refill()

  • 当free-list没有可用区块的时候,refill()从内存池中获取新的空间。

  • 缺省获得20个区块大小的空间,若内存池空间不足,实际获取的区块可能不足20。

  • 源码

    //假设n已经上调至8的倍数
    template <bool threads, int inst>
    void* __default_alloc__template<threads,inst>::refill(size_t n)
    {
        int nobjs = 20;
        //调用chunk_alloc(),尝试获得nobjs个区块作为free-list的新节点
        //注意参数nobjs是pass by reference
        char* chunk = chunk_alloc(n,nobjs);
        obj * volatile * my_free_list;
        obj * result;
        obj * current_obj, * next_obj;
        int i;
        
        //如果只有一个区块,这个区块就分配给调用者用,free-list无新节点
        if(n==1) return (chunk);
        //否则准备调整free-list
        my_free_list = free_list + FREELIST_INDEX(n);
        
        //以下在chunk空间内建立free-list
        result = (obj*) chunk;	//这一块准备返回给客端
        //以下引导free-list指向新配置的空间
        *my_free_list = next_obj = (obj*)(chunk+n);
        //以下将free-list的各节点串联起来
        for(i=1;;i++){
            current_obj = next_obj;
            next_obj = (obj*)((char*)next_obj+n);
            if(nobjs-1 == i){
                current_obj->free_list_link = 0;
                break;
            }else{
                current_obj->free_list_link = next_obj;
            }
        }
        return (result);
    }
    

2.2.10 内存池

  • chunk_alloc()从内存池中取空间给free-list使用,在refill()中被调用。

  • template <bool threads, int inst>
    char*
    __default_alloc_template__<threads,inst>::
    chunk_alloc(size_t size, int& nobjs)
    {
        //···
    }
    
  • chunk_alloc()的格式如上面所示,根据传进来的sizenobjs分配空间。

  • 首先在__default_alloc_template__类中有两个指针:start_freeend_free,两个指针都是static char*格式,分别指向内存池起始位置和内存池结束位置。

  • chunk_alloc()end_free-start_free判断内存池的水量,如果水量充足,就直接调出20个区块返回给free-list。

  • 如果水量不足以提供20个区块,但还足够提供一个以上的区块,就拨出这不足20个区块的空间出去。此时pass by reference的nobjs参数将被修改为实际能提供的区块数。

  • 如果内存池连一个区块空间都不够了,就调用malloc从heap中配置内存,为内存池注入源头活水以应付需求。

  • 万一山穷水尽,整个system heap空间都不够了,malloc失败,chunk_alloc就四处寻找有无“尚有未用区块,且区块足够大”之free-list。找到了就挖一块交出,找不到就调用第一级配置器(寄希望于第一级配置器的out-of-memory处理机制)。如果可以就成功,否则就抛出bad_alloc异常。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章