STL之分配器

分配器allocator的实现

1. 在vc98中allocaters的实现

1.new调用malloc函数

image-20201118113552992

2.allocate调用new函数

image-20201118113652668

2.在BC5中allocator的使用

image-20201118123206761

实现原理image-20201118123353208

3.G++2.9stl

1. 对allocator的使用(使用alloc)

image-20201118123633818

注意 他有allocator但没有放入标准库里,而是使用了alloc

image-20201118123745472

2.alloc的实现原理

设计了十六条链表,每一条链表负责某一个固定大小的区块,

image-20201118115903770

为什么不建议使用分配器

分配器的申请的释放底层实现实现就是malloc和free

在释放时使用deallocate还需要制定当初申请时的空间大小,对于程序员是极不友好的。

image-20201118121023827

4.G++4.9版本

1.使用了新定义的alloctor(放弃了设计较好的alloc)

image-20201118124031513

2.新扩展的pool_allocator就是原来的alloc

image-20201118124342201

空间配置器

从stl运用的角度来看,空间配置器是最不需要介绍的东西,他总是隐藏在一切组件(容器)的背后

从stl实现的角度来看,第一个就需要了解空间配置器,整个stl的操作对象都存放在容器之内。

为什么说是空间配置器而不说成内存配置器?

因为空间不一定是内存,空间可以是磁盘也可以是其他辅助存储介质,空间包含的范围大。

配置器的接口

allocator: :value_type

allocator: :pointer

allocator::const_point er

allocator::reference

allocator::const_reference

allocator::size_type

allocator::difference_type

allocator: :rebind

allocator函数

image-20201120213813811

拥有次层配置的SGI空间配置器(重点)

在个版本中SGI STL逸脱了STL标准,使用一个专属的,有次层配置的特殊配置器,它提供了一个标准的配置器接口(simple_alloc),对它进行了一层隐藏。

与标准配置器不同

SGI STL的配詈器与众不同, 也与标准规范不同, 其名称是alloc而非allocator,而且不接受任何参数。换句话说,如果你要在程序中明白采用SGI配置器, 则不能采用

标准写法:

vector<int, std::allocator > iv; // in VC or CB

SGI写法:

vector<in七, std: :alloc> iv; // in GCC

SGI并不会带来麻烦,我们通常使用缺省的很少需要自行指定配置器名称,而SGISTL的 每一个容器都已经指定其缺省的空间配置器为alloc。

template <class T, class Alloc = alloc> //缺省使用alloc为配置器

class vector {… };

SGI配置器 std::alloc

一般而言, 我们所习惯的C++内存配置操作和释放操作是这样的:

class Foo {… } ;

Foo* pf= new Foo; //配置内存,然后构造对象

delete pf; //将对象析构,然后释放内存

包含

1.配置内存(分配器内存)/ 释放内存

2.构造对象内容 / 对象析构

STLallocator决定将这两阶段操作区分开来。

内存配置操作由alloc:allocate() 负责,内存释放操作由alloc: :deallocate() 负责;

对象构造操作由: : construct () 负责,对象析构操作由: : destroy () 负责。

#include <stl_alloc.h> 负责内存空间的配置与释放
#include <stl_construct.h> 负责对象内容的构造与析构

image-20201120215429133

construct和destroy函数(对象构造和析构)
定义

image-20201120221323325

image-20201120221344900

解释

construct()接受一个指针p和一个初值value, 该函数的用途就是将初值设定到指针所指的空间上。

destroy()有两个版本, 第一版本接受一个指针,准备将该指针所指之物析构掉。第二版本接受first和last 两个迭代器准备将[first, last )范围内的所有对象析构掉。(如果对象很多,将会花费大量的时间,)

对destroy进行优化(判断value_type)

因此, 这里首先利用value_type()获得迭代器所指对象的 型别, 再利用—type_traits 判断该型别的析构函数是否无关痛痒。 若是
(_true_type), 则什么也不做就结束;若否(—false_type), 这才以循环方式巡访整个范围, 并在循环中每经历一个对象就调用第一个版本的des七roy()。

空间的分配和释放(由<stl_alloc.h>负责)

SGI对此的设计哲学:

• 向systemheap要求空间。
• 考虑多线程(multi-threads)状态。
• 考虑内存不足时的应变措施。

C++的内存配置基本操作是 ::operator new(),内存释放基本操作是: :operator delete()。

这两个全局函数相当于C的malloc()和free()函数,SGI正是以malloc()和free()完成内存的配置与释放。

解决内存破碎问题

考虑到小型区块所可能造成的内存破碎问题,SGI设计了双层级配置器,第 级配置器直接使用malloc()和free(),第二级配置器则视情况采用不同的策略:

  1. 当配置区块超过 128 bytes时, 视之为 "足够大” ,便涸用第一级配置器;
  2. 当配 置区块小于 128 bytes时, 视之为 “过小” ,为了降低额外负担 便采用复杂的内存池整理方式。

image-20201120222431461

_malloc_alloc_template就是第 一 级配置器

_default_alloc_ template就是第二级配置器。

注意:alloc并不接受任何template型别参数。

为alloc包装接口simple_alloc

SGI还为它再包装一个接口如下, 使配置器的接口能够符合STL规格:

image-20201120222726305

一二级配置的关系

image-20201120222815766

image-20201120222837626

第一级配置器解析
#if 0   
#   include <new>   
#   define  __THROW_BAD_ALLOC throw bad_alloc   
#elif !defined(__THROW_BAD_ALLOC)   
#   include <iostream.h>   
#   define  __THROW_BAD_ALLOC cerr << "out of memory" << endl; exit(1)   
#endif   

// malloc-based allocator. 通常比稍后介绍的 default alloc 速度慢,   
//一般而言是 thread-safe,并且对于空间的运用比较高效(efficient)。   
//以下是第一级配置器。   
//注意,无「template 型别参数」。至于「非型别参数」inst,完全没派上用场。  
template <int inst>     
class __malloc_alloc_template {   

private:   
//以下都是函式指标,所代表的函式将用来处理内存不足的情况。   
// oom : out of memory.   
static void *oom_malloc(size_t);   
static void *oom_realloc(void *, size_t);   
static void (* __malloc_alloc_oom_handler)();   

public:   

static void * allocate(size_t n)   
{   
    void  *result =malloc(n);//第一级配置器直接使用 malloc()   
    // 以下,无法满足需求时,改用 oom_malloc()   
    if (0 == result) result = oom_malloc(n);   
    return  result;   
}   
static void deallocate(void *p, size_t /* n */)   
{   
free(p); //第一级配置器直接使用 free()   
}   

static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz)   
{   
    void  *  result  =realloc(p, new_sz);//第一级配置器直接使用 rea  
    // 以下,无法满足需求时,改用 oom_realloc()   
    if (0 == result) result = oom_realloc(p, new_sz);   
    return  result;   
}   

//以下模拟 C++的 set_new_handler(). 换句话说,你可以透过它,   
//指定你自己的 out-of-memory handler   
static void (* set_malloc_handler(void (*f)()))()   
{   
    void  (*  old)()  =  __malloc_alloc_oom_handler;   
__malloc_alloc_oom_handler = f;   
    return(old);   
}   
};   

// malloc_alloc out-of-memory handling   
//初值为 0。有待客端设定。   
template <int inst>   
void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0;   

template <int inst>   
void * __malloc_alloc_template<inst>::oom_malloc(size_t n)   
{   
    void  (* my_malloc_handler)();   
    void  *result;   

    for (;;)  {   

//不断尝试释放、配置、再释放、再配置…   
my_malloc_handler = __malloc_alloc_oom_handler;   
        if  (0  ==  my_malloc_handler)  {  __THROW_BAD_ALLOC; }   
        (*my_malloc_handler)();//呼叫处理例程,企图释放内存。   
        result = malloc(n);  //再次尝试配置内存。   
        if  (result)  return(result);   
    }   
}   

template <int inst>   
void * __malloc_alloc_template<inst>::oom_realloc(void *p, size_t n)   
{   
    void  (* my_malloc_handler)();   
    void  *result;   
       for (;;)  {  //不断尝试释放、配置、再释放、再配置…   
my_malloc_handler = __malloc_alloc_oom_handler;   
        if  (0  ==  my_malloc_handler)  {  __THROW_BAD_ALLOC; }   
        (*my_malloc_handler)();//呼叫处理例程,企图释放内存。   
        result = realloc(p, n);//再次尝试配置内存。   
        if  (result)  return(result);   
    }   
}   

//注意,以下直接将参数 inst指定为 0。   
typedef __malloc_alloc_template<0> malloc_alloc;   

SGI第一级配置器的allocate()和realloc()都是在调用malloc()和realloc()不成功后,改调用oom_malloc()和oom_realloc()。后两者都有内循环,不断调用“内存不足处理例程”,期望在某次调用之后,获得足够的内存而圆满完成任务。但如果“内存不足处理例程”并未被客端设定,调用THROW_BADALLOC,丢出bad_alloc异常信息,或利用exit(1)硬生生中止程序。

用于采用的是malloc free realloc函数,执行实际的内存配置,释放,重配等操作,实现类似于C++new-handler7的机制。

他不能直接运用C++new-handler7的机制,因为他并非使用::operate:new来配置内存。

C++new-handler7的机制是什么

你可以要求系统在内存配置需求无法被满足时,调用一个你所指定的函数。换句话说,一旦::operate new无法完成任务, 在丢出std::bad_alloc异常状态之前,会先调用由客端指定的处理例程。这个例程就是new-handler

第二级配置器解析(次层配置)

image-20210331101747401

如果区块超过128k,移交第一级,否则就以内存池管理

每次配置一大块内存,维护其自由链表,下次若再有相同大小的内存需求,就直接从free-lists中拨出。如果客端释还小额区块,就由配置器回收到free-lists中(配置器既负责创建又负责回收)

为了方便管理,将内存需求设置为8的倍数一共有16个自由链表,从8-128.

用户申请空间的工作流程

首先将这个空间大小上调到8的倍数,找到对应的free-list

  1. 如果链表有空闲节点,直接取下一个节点分配给用户。

  2. 对于你那个链表为空,重新申请链表的节点(默认为20个此大小的节点)

  3. 不足20个,但是可以支付一个或者更多该节点的大小,返回可完成的节点个数。(能给多少给多少)

  4. 没有办法满足,重新申请内存池,

    所申请的内存池大小为2*total_bytes+ROUND_UP(heap_size>>4),total_bytes是所申请的内存大小,SGI将申请2倍还要多的内存。为了避免内存碎片问题,需要将原来内存池中剩余的内存分配给free_list链表。

    1. 如果内存池申请失败,也就是heap_size(堆空间)不足以支付要求时,SGI的次级配置器将使用最后的绝招查看free_list数组,查看是否有足够大的没有被使用的内存
    2. 如果这些办法都没有办法满足要求时,只能调用第一级配置器了,我们需要注意,第一级配置器虽然是用malloc来分配内存,但是有new-handler机制(out-of-memory),如果无法成功,只能抛出bad_alloc异常而结束分配。

free-lists节点定义如下:

union obj
{
    union obj* free_list_link;
    char client_data[1];
};
   enum {__ALIGN = 8};  //小型区块的上调边界
enum {__MAX_BYTES = 128};   //小型区块的上界
enum {__NFREELISTS = __MAX_BYTES/__ALIGN};   //free-list个数

 // 无template型参数,且第二个参数没有用上
 // 第一个用于多线程,暂不讨论
template <bool threads, int inst>
class __default_alloc_template {

private:
    /*将bytes上调至8的倍数
    用二进制理解,byte整除align时尾部为0,结果仍为byte;否则尾部肯定有1存在,加上
    align - 1之后定会导致第i位(2^i = align)的进位,再进行&操作即可得到8的倍数
    */
    static size_t ROUND_UP(size_t bytes) {
        return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));
    }
private:
    union obj {   //free-list的节点
        union obj * free_list_link;
        char client_data[1];    /* The client sees this.     */
    };

private:
    //16个free-lists
    static obj * __VOLATILE free_list[__NFREELISTS]; 
    //根据区块大小,找到合适的free-list,返回其下标(从0起算)
    static  size_t FREELIST_INDEX(size_t bytes) {
        return (((bytes) + __ALIGN-1)/__ALIGN - 1);
  }

  //返回一个大小为n的对象,并可能编入大小为n的区块到相应的free-list
  static void *refill(size_t n);
  //配置一大块空间,可容纳nobjs个大小为“size”的区块
  //如果配置nobjs个区块有所不便,nobjs可能会降低
  static char *chunk_alloc(size_t size, int &nobjs);

  //Chunk allocation state
  static char *start_free;
  static char *end_free;
  static size_t heap_size;

public:
    // 下面会介绍
    static void * allocate(size_t n); 
    static void * deallocatr(void *p, size_t n);
    static void * reallocate(void *p, size_t old_sz, size_t new_sz);
};

//以下是static data member的定义与初值设定
template <bool threads, int inst>
char * __default_alloc_template<threads, inst>::start_free = 0;

template <bool threads, int inst>
char * __default__alloc_template<threads, inst>::end_free = 0;

template <bool threads, int inst>
size_t __default_alloc_template<threads, inst>::heap_size = 0;

template <bool threads, int inst>
__default_alloc_template<threads, inst>::obj * volatile
__default_alloc_template<threads, inst>::free_list[__NFREELISTS] = 
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };

allocate函数
 static void * allocate(size_t n)
  {
    obj * __VOLATILE * my_free_list;
    obj * __RESTRICT result;

    if (n > (size_t) __MAX_BYTES) {
        return(malloc_alloc::allocate(n));
    }

    //如果所开辟的区块的大小小于128bytes
    my_free_list = free_list + FREELIST_INDEX(n);
    result = *my_free_list;
    if (result == 0) {
        // 没有找到可用的free list,重新配置
        void *r = refill(ROUND_UP(n));     // refill会在后续介绍
        return r;
    }
    //如果此时free_list上有空间,则拨出一块空间给对象使用
    *my_free_list = result -> free_list_link;
    return (result);
  };

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4wxaT7Lx-1617158146989)(C:/Users/Godfiry/AppData/Roaming/Typora/typora-user-images/image-20201124104946551.png)]

deallocate函数
static void deallocate(void *p, size_t n)
{
    obj *q = (obj *)p;
    obj * __VOLATILE * my_free_list;

    if (n > (size_t) __MAX_BYTES) {
        malloc_alloc::deallocate(p, n);
        return;
    }

    //如果是小内存块的释放,则还是先找到所释放内存块在free_list[]中的位置
    my_free_list = free_list + FREELIST_INDEX(n);
    q -> free_list_link = *my_free_list;

    // 调整free_list上的首地址,即返还给free_list中的这个节点
    *my_free_list = q;
}

image-20201124105208615

refill函数重新填充

当它发现free list中没有可用区块了时,就调用refill(),准备为free list重新填充空间。 新的空间将取自内存池(经一
chunk_alloc()完成)。缺省取得20个新节点(新区块),但万一不够,可能小于20个。

template <bool threads, int inst>
void* __default_alloc_template<threads, inst>::refill(size_t n)
{
    // 默认填充20个(n字节上调至8的整数倍)的内存块
    int nobjs = 20;

    // 这个函数的作用是尝试取得nobjs个(n字节上调至8的整数倍)的内存块作为free_list的新节点
    // 这里需要注意取得的不一定是20个区块,如果内存池的空间不够,它所获得的区块数目可能小于20个
    char * chunk = chunk_alloc(n, nobjs);
    obj * __VOLATILE * my_free_list;
    obj * result;
    obj * current_obj, * next_obj;
    int i;

    // 如果申请到的区块数目为1,则直接返还给对象使用
    if (1 == nobjs) return(chunk);

    // 如果不为1则找到区块在free_list[]中所对应位置
    my_free_list = free_list + FREELIST_INDEX(n);

    //从头拨出1个申请好的区块在下面返还给对象,把剩余的区块全部链在free_list[FREELIST_INDEX]下面
    result = (obj *)chunk;
    *my_free_list = next_obj = (obj *)(chunk + n);
    for (i = 1; ; i++) {
        current_obj = next_obj;

        //chunck_alloc返回的空间类型为char*
        next_obj = (obj *)((char *)next_obj + n);
        //如果已经链上的节点的个数等于从内存池申请的节点的个数-1,终止循环
        if (nobjs - 1 == i) {
            current_obj -> free_list_link = 0;
            break;
        } else {
            current_obj -> free_list_link = next_obj;
        }
    }

    return(result);
}

内存池

refill函数中使用到的chunk_alloc,就是从内存池中获取空间,以下为具体实现:

template <bool threads, int inst>
char*
__default_alloc_template<threads, inst>::chunk_alloc(size_t size, int& nobjs)
{
    char * result;
    size_t total_bytes = size * nobjs;
    size_t bytes_left = end_free - start_free;  // 内存池剩余大小

    if (bytes_left >= total_bytes) {
        // 剩余大小满足申请需求
        result = start_free;
        start_free += total_bytes;
        return(result);
    }
    else if (bytes_left >= size) {
        // 剩余大小不满足申请需求,但是能够供应一个区块以上的大小,也就是refill里说明的获得的区块数目可能小于20个
        nobjs = bytes_left/size;
        total_bytes = size * nobjs;
        result = start_free;
        start_free += total_bytes;
        return(result);
    }
    else {
        // 剩余大小连一个区块都不满足
        size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);

        // 尝试内存池中剩余的小空间分配给合适的free-lists
        if (bytes_left > 0) {
            obj * __VOLATILE * my_free_list = free_list + FREELIST_INDEX(bytes_left);
            ((obj *)start_free) -> free_list_link = *my_free_list;
            *my_free_list = (obj *)start_free;
        }

        // 用malloc给内存池分配空间
        start_free = (char *)malloc(bytes_to_get);
        if (0 == start_free) {
            // 分配失败
            int i;
            obj * __VOLATILE * my_free_list, *p;

            // 在free_lists中查找没有使用过的内存块,并且它足够大
            for (i = size; i <= __MAX_BYTES; i += __ALIGN) {
                my_free_list = free_list + FREELIST_INDEX(i);
                p = *my_free_list;

                // 把free-lists中的内存编入内存池
                if (0 != p) {
                    *my_free_list = p -> free_list_link;
                    start_free = (char *)p;
                    end_free = start_free + i;
                    return(chunk_alloc(size, nobjs));  // 递归调用,剩余的的零头会被重新编入合适的free-lists
                }
            }

        // 如果free_list中也没有内存块了
        end_free = 0;

        // 试着调用一级空间配置器,可能会抛出异常或者申请到内存
        start_free = (char *)malloc_alloc::allocate(bytes_to_get);
        }

        heap_size += bytes_to_get;
        end_free = start_free + bytes_to_get;
        return (chunk_alloc(size, nobjs));
    }
}

image-20201124110716487

总结

STL的分配器用于封装STL容器在内存管理上的底层细节。在C++中,其内存配置和释放如下:

new运算分两个阶段: ⑴调用::operator new配置内存; (2)调用对象构造函数构造对象内容
delete运算分两个阶段: ⑴调用对象希构函数;(2)掉员工::operator delete释放内存

为了精密分工,STL allocator将两个阶段操作区分开来:

内存配置有alloc::allocate()负责,内存释放由alloc:: deallocate()负责;
对象构造由::construct()负责,对象析构由::destroy()负责。

同时为了提升内存管理的效率,减少申请小内存造成的内存碎片问题,SGI STL 采用了两级配置器

当分配的空间大小超过128B时,会使用第一级空间配置器;当分配的空间大小小于128B时,将使用第二级空间配置器。
第一级空间配置器直接使用malloc()、realloc()、free()函数进行内存空间的分配和释放,而第二级空间配置器采用了内存池技术,通过空闲链表来管理内存。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值