STL 之 空间配置器(allocator)

一、SGI 标准的空间配置器,std::allocator

SGI也定义了一个符合部分标准,名为allocator的配置器,但是它自己不使用,也不建议我们使用,主要原因是效率不佳。

它只是把C++的操作符::operator new和::operator delete做了一层简单的封装而已。

二、SGI 特殊的空间配置器,std::alloc

由于SGI 标准的空间配置器只是把C++的操作符::operator new和::operator delete做了一层简单的封装,没有考虑到任何效率上的强化。

所以 SGI 出现了一个特殊的空间配置器,供 STL标准模板库 使用。

通常,C++中用new操作符来分配内存都包括两个阶段:

(1)调用::operator new配置内存

(2)调用构造函数来构造对象内容

同理,delete操作也包括两个阶段:

(1)调用析构函数将对象析构

(2)调用::operator delete释放内存

为了精密分工,SGI STL allocator将两个阶段分开

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

配置器定义在头文件<memory>中,它里面又包括两个文件:

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

STL 空间配置器:

STL 空间配置器解决的问题

1:内存碎片问题(外碎片)

内碎片,外碎片简介:

内碎片:操作系统在分配给申请者未被利用的部分。产生原因:内存对齐(效率高);

外碎片:内存充足时但分配不出大块内存。产生原因:由于多次分配小块内存,将大块内存分割成小块;

2.频繁系统分配,释放小块内存,调用 malloc,delete 系统调用产生性能问题;

STL 空间配置器的实现机制:

双层级的配置器:

注:

当需要开辟的空间大于 128 bytes 时,视为“足够大”,调用一级配置器,直接调用 malloc,free;

当需要开辟的空间小于 128 bytes 时,视为“过小”,为了降低额外负担,调用二级空间配置器

一级空间配置器的剖析:

(set_new_handler 机制)

/*****************************************************************************
功能描述:一级空间配置器

类 /函数:template<int inst>  class __MallocAllocTemplate;

简    介:直接调用 malloc/free,内存分配失败后调用句柄处理函数
*****************************************************************************/

typedef void(*ALLOC_OOM_FUN)();  //内存分配失败以后处理的句柄handler类型
template<int inst>
class __MallocAllocTemplate
{
private:
    static ALLOC_OOM_FUN __sMallocAllocOomHandler;
    static void* OomMalloc(size_t n)   //模仿 c++ 的 set_new_handler 机制
    {
        ALLOC_OOM_FUN handler = __sMallocAllocOomHandler;
        void* result;

        for (;;)  //一直循环,直到内存分配成功为止
        {
            if (handler == 0)  //检查是否设置处理的 handler
            {
                cout << "out of memory " << endl;
                exit(-1);
            }
            handler();    //调用客户自己的内存管理函数,释放内存
            result = malloc(n);
            if (result)        //分配成功
                return result;
        }
    }

    static void* OomRealloc(void*p, size_t n)
    {
        ALLOC_OOM_FUN handler = __sMallocAllocOomHandler;
        void* result;

        for (;;)  //一直循环,直到内存分配成功为止
        {
            if (handler == 0)  //检查是否设置处理的 handler
            {
                cout << "out of memory " << endl;
                exit(-1);
            }
            handler();    //调用客户自己的内存管理函数,释放内存
            result = remalloc(n);
            if (result)        //分配成功
                return result;
        }
    }

public:
    static void* Allocate(size_t n)  //直接调用 malloc
    {
        void* result = malloc(n);
        return result == 0 ? OomMalloc(n) : result;
    }

    static void Deallocate(void* p)   //直接 free
    {
        free(p);
    }

    static void *Reallocate(void*p,size_t new_sz)
    {
        void* result = realloc(p, new_sz);
        return result == 0 ? OomRealloc(p, new_sz) : result;
    }

    static void(*SetMallocHandler(void(*f)()))()    //重新设置句柄处理函数
    {
        ALLOC_OOM_FUN result = __sMallocAllocOomHandler;
        __sMallocAllocOomHandler = f;
        return result;
    }
};

//初始化分配内存失败处理函数的句柄函数指针
template<int inst>
ALLOC_OOM_FUN __MallocAllocTemplate<inst>::__sMallocAllocOomHandler = 0;

二级空间配置器:

template<bool threads, int inst>
class __DefaultAllocTemplate
{
public:
    enum{ __ALIGN = 8 };                        //排列基准值(也是排列间隔)
    enum{ __MAX_BYTES = 128 };                   //最大值
    enum{ __NFREELISTS = __MAX_BYTES / __ALIGN };    //排列链大小
    static size_t ROUND_UP(size_t bytes)
    {
        return (bytes + __ALIGN - 1 & ~(__ALIGN - 1));  //内存与 8 对齐
    }
    static size_t FREELIST_INDEX(size_t bytes)
    {
        return ((bytes + __ALIGN - 1) / __ALIGN - 1);  //返回在自由链表中的下标
    }

    union Obj
    {
        union Obj* _freeListLink;   //指向下一个内存块的指针
        char _clientData[1];   /* The client sees this.*/
    };

    static Obj* volatile _freeList[__NFREELISTS];    //自由链表
    static char* _startFree;                        //内存池起始地址
    static char* _endFree;                          //内存池结束地址
    static size_t _heapSize;                        //内存池的容量   

    //从内存池中获取内存插入到自由链表中
    static void* Refill(size_t n);
    //从内存池中分配内存
    static char* ChunkAlloc(size_t size, int& nobjs);
    //用户调用申请内存函数
    static void* Allocate(size_t n);
    //内存释放函数
    static void Deallocate(void*p, size_t n);
    //重新分配内存
    static void* Reallocate(void*p, size_t old_sz, size_t new_sz);
};

//初始化全局静态对象
//1.自由链表
template<bool threads, int inst>
typename __DefaultAllocTemplate<threads, inst>::Obj* volatile
__DefaultAllocTemplate<threads, inst>::_freeList[__DefaultAllocTemplate<threads, inst>::__NFREELISTS];
//2.内存池
template<bool threads, int inst>
char* __DefaultAllocTemplate<threads, inst>::_startFree = 0;
template<bool threads, int inst>
char* __DefaultAllocTemplate<threads, inst>::_endFree = 0;
template<bool threads, int inst>
char* __DefaultAllocTemplate<threads, inst>::_heapSize = 0;

//类中声明函数的定义
template<bool threads, int inst>
void* __DefaultAllocTemplate<threads, inst>::Refill(size_t n)
{
    int nobjs = 20;
    char* result = ChunkAlloc(n, nobjs);
    if (nobjs == 1)
        return result;
    Obj* cur = (Obj*)(result + n);
    size_t index = FREELIST_INDEX(n);
        _freeList[index] = cur;
    for (int i = 2; i < nobjs; ++i)  //链表的头插
    {
        cur->_freeListLink = i*n + result;
        cur = cur->_freeListLink;
    }
    cur->_freeListLink = NULL;
    return result;
}

template<bool threads, int inst>
char* __DefaultAllocTemplate<threads, inst>::ChunkAlloc(size_t size, int&nobjs)
{
    char* result;
    size_t bytesNeed = size*nobjs;
    size_t bytesHave = _endFree - _startFree;

    if (bytesHave >= bytesNeed)   //内存池中所剩内存大于需要的内存
    {
        result = _startFree;
        _startFree += bytesNeed;
    }
    else if (bytesHave >= size)   //至少能满足一个 size 的分配
    {
        result = _startFree;
        nobjs = bytesHave / size;
        _startFree += size*nobjs;
    }
    else      //内存池中所剩空间不够一个 size
    {
        //清空内存池,准备往内存池中补充空间
        Obj* cur = _startFree;
        size_t index = FREELIST_INDEX(bytesHave);
        cur->_freeListLink = _freelist[index];
        _freelist[index] = cur;
        _startFree = NULL, _endFree = NULL;

        //重新向系统申请 bytesNeed*2 + _heapSize/16
        size_t bytesToGet = bytesNeed * 2 + (_heapSize >> 4);
        _startFree = (char*)malloc(bytesToGet);

        if (_startFree == NULL)    //向系统申请内存失败
        {
            //先向自由链表上游中的大块内存链中寻找
            for (int i = size; i <= __MAX_BYTES; i += __ALIGN)
            {
                Obj* head = _freeList[FREELIST_INDEX(size)];
                if (head)
                {
                    _startFree = (char*)head;
                    _endFree = (char*)head + i;
                    _freeList[FREELIST_INDEX(size)] = head->_freeListLink;
                    return ChunkAlloc(size, nobjs);
                }
            }
            //链表中也没有了,实在无奈,调用一级配置器中的内存分配函数
            _startFree = (char*)MallocAlloc::Allocate(byteToGet);
        }

        _heapSize += bytesToGet;
        _endFree = _startFree + bytesToGet;
        return ChunkAlloc(size, nobjs);  //函数的复用
    }
    return result;
}

template<bool threads, int inst>
void* __DefaultAllocTemplate<threads, inst>::Allocate(size_t n)
{
    void* result;
    if (n > __MAX_BYTES)
        result = MallocAlloc::Allocate(n);
    //
    //自由链表有,就直接取
    //没有,就通过 Refill进行填充
    //
    size_t index = FREELIST_INDEX(n);
    if (_freeList[index])
    {
        result = _freeList[index];
        _freelist[index] = ((Obj*)result)->_freeListLink;
    }
    else
    {
        result = Refill(ROUND_UP(n));
    }

    return result;
}

template<bool threads, int inst>
void __DefaultAllocTemplate<threads, inst>::Deallocate(void*p, size_t n)
{
    if (n > __MAX_BYTES)
        MallocAlloc::Deallocate(p);
    //不大于 __MAX_BYTES ,将此空间插回自由链表
    else
    {
        size_t index = FREELIST_INDEX(n);
        Obj* cur = (Obj*)p;
        cur->_freeListLink = _freeList[index];
        _freeList[index] = cur;
    }
}

template<bool threads, int inst>
void* __DefaultAllocTemplate<threads, inst>::Reallocate(void* p, size_t old_sz, size_t new_sz)
{
    void* result;
    if (old_sz > (size_t)__MAX_BYTES && new_sz > (size_t)__MAX_BYTES)    //
        return realloc(p, old_sz, new_sz);
    if (ROUND_UP(old_sz) == ROUND_UP(new_sz))
        return p;
    result = Allocate(new_sz);
    size_t size = old_sz > new_sz ? old_sz : new_sz;
    meycpy(result, p, size);
    Deallocate(p, old_sz);
    return result;
}

typedef __DefaultAllocTemplate<false, 0> Alloc;
#endif // __USE_MALLOC

二级空间配置器

STL 空间配置器的缺点:

1.有内碎片问题

2.内存池中的内存在程序结束前不能还给操作系统,被分割为小块的内存一直挂在自由链表下面,会使得内存的占用率过高。

学习ing,如果又发现哪有写的不对的地方请不吝赐教~~

时间: 2024-08-11 01:19:07

STL 之 空间配置器(allocator)的相关文章

C++ STL学习之 空间配置器(allocator)

标签(空格分隔): C++ STL 众所周知,一般情况下,一个程序包括数据结构和相应的算法,而数据结构作为存储数据的组织形式,与内存空间有着密切的联系. 在C++ STL中,空间配置器便是用来实现内存空间(一般是内存,也可以是硬盘等空间)分配的工具,他与容器联系紧密,每一种容器的空间分配都是通过空间分配器alloctor实现的.理解alloctor的实现原理,对内存结构以及数据存储形式会有更清晰直观的认识. 1.两种C++类对象实例化方式的异同 在c++中,创建类对象一般分为两种方式:一种是直接

STL源码剖析 — 空间配置器(allocator)

前言 以STL的实现角度而言,第一个需要介绍的就是空间配置器,因为整个STL的操作对象都存放在容器之中. 你完全可以实现一个直接向硬件存取空间的allocator. 下面介绍的是SGI STL提供的配置器,配置的对象,是内存.(以下内容来自<STL源码剖析>) 空间配置器的标准接口 根据STL的规范,allocator的必要接口 各种typedef 1 allocator::value_type 2 allocator::pointer 3 allocator::const_pointer 4

STL学习笔记--2、空间配置器 allocator

2.1标准接口 allocator::value_type allocator::pointer allocator::const_pointer allocator::reference allocator::const_reference allocator::size_type allocator::difference_type allocator::rebind allocator::allocator()//默认构造函数 allocator::allocator(const allo

STL源码剖析——空间配置器Allocator#2 一/二级空间配置器

上节学习了内存配置后的对象构造行为和内存释放前的对象析构行为,在这一节来学习内存的配置与释放. C++的内存配置基本操作是::operator new(),而释放基本操作是::operator delete().这两个全局函数相当于C的malloc() 和free() 函数.而SGI正是以malloc() 和free() 完成内存的配置与释放. 考虑到小型区块可能造成的内存破碎问题,SGI设计了两级的空间配置器.第一级直接使用malloc() 和free() ,而第二级则视情况采用不同的策略:当

(一)STL剖析——空间配置器

Alloc.h //Alloc.h负责内存空间的配置与释放 //Construct.h负责对象内容的构造与析构 //这两个头文件在memory文件中包含 #pragma once typedef void(*HANDLER_FUNC)(); //一级空间配置器 template <int inst> class __MallocAllocTemplate  { public: static void* Allocate(size_t size)//静态成员函数 { void* result =

【STL】空间配置器剖析(二)

上篇文章点击打开链接主要对于对象的构造含和析构进行了主要说明,这篇文章将对对象构造前的内存配置和对象析构后的空间释放进行深入探索. 好的,话不多说马上进入是正文: 对对象构造前的内存配置和对象析构后的空间释放,由<stl_alloc.h>负责,SGI对此的设计哲学如下: 向system heap要求空间. 考虑多线程的状态 考虑内存不足的应变措施 考虑过多的"小型区块"可能造成的内存碎片问题 C++的内存配置的基本操作是:operator new(),内存释放的基本操作是o

C++标准库——STL之空间配置器

声明:源码同<STL源码剖析>(侯捷) STL: C++标准的模板库,通用性高. 常见的数据结构封装. 提供常用的通用算法. STL六大组件: 容器      算法       迭代器       仿函数(函数对象)     适配器      配置器 空间配置器的作用: 1.提高代码复用率,功能模块化. 2.减少内存碎片问题. 比如我们list是链式结构,如果其中的成员是一个个new出来的,我们知道new的底层实现是malloc,同时也必须清楚,当malloc一块40字节空间时,操作系统为了能

STL源码剖析——空间配置器Allocator#3 自由链表与内存池

上节在学习第二级配置器时了解了第二级配置器通过内存池与自由链表来处理小区块内存的申请.但只是对其概念进行点到为止的认识,并未深入探究.这节就来学习一下自由链表的填充和内存池的内存分配机制. refill()函数——重新填充自由链表 前情提要,从上节第二级配置器的源码中可以看到,在空间配置函数allocate()中,当所需的某号自由链表为空时,才会调用refill()函数来填充链表.refill()函数默认申请20块区块的内存(5行),但所得内存不一定就是20块,要看当前内存池的剩余情况和堆容量的

[C++] 空间配置器——allocator类

1.new和delete有一些灵活性上的局限:new把内存分配和对象构造组合在了一起:delete将对象析构和内存释放组合在了一起. 2.当分配一大块内存时,我们通常计划在这块内存上按需构造对象,在此情况下,我们希望将内存分配和对象构造分离:这意味着我们可以分配大块内存,但只在真正需要的时候才真正执行对象创建操作. 3.allocator类,定义在头文件memory中,它帮助我们将内存分配和对象构造分离开来,它提供一种类型感知的内存分配方法,它分配的内存是原始的.未构造的.在分配内存时,它会根据