目录

1. 空间配置器概述

2. 构造和析构基本工具

3. 空间的配置与释放,std::alloc

4. 内存基本处理工具


1. 空间配置器概述

从STL的实现角度来看,空间配置器的位置尤为重要,整个STL的操作对象(所有的数值)都是存放在容器之内,而容器一定需要配置空间以存放资料。空间配置器就是为各个容器提供高效的管理空间。SGI STL的配置器与众不同,也与标准规范不同,其名称为alloc而非allocator,而且不接受任何参数。

不能采用标准写法:vector> iv;  // inv VC or CB应该写成:vector iv;    // in GCC而且在使用容器中,一般很少指定配置器名称,这时候默认缺省的配置器为alloc,例如:template   //缺省使用alloc为配置器class vector {...};其实SGI也存在标准的空间配置器std::allocator,但是因为效率不高,也不建议使用

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

class A {};A* pa = new A;//...执行其他操作delete pa;

这过程中的new算式内含两阶段操作:(1)调用 ::operator new 配置内存;(2)调用 A::A() 构造对象内容。delete算式也内含两阶段操作:(1)调用 A::~A() 将对象析构;(2)调用 ::operator delete 释放内存。但是,为了最大化提升效率,SGI STL版本并没有简单的这样做,而是采取了一定的措施,实现了更加高效复杂的空间分配策略。在SGI STL中,将对象的构造切分开来,分成空间配置和对象构造两部分:

  • 内存配置操作:alloc::allocate()实现
  • 内存释放操作:alloc::deallocate()实现
  • 对象构造操作: ::construct()实现
  • 对象释放操作: ::destroy()实现

STL对象构造与释放结构图如下所示:

2. 构造和析构基本工具

下面是的部分内容,介绍了construct和destroy函数。

template inline void construct(T1* p, const T2& value) {  new (p) T1(value);  // placement new; 唤起T1::T1(value)}// 第一个版本,接受一个参数template inline void destroy(Tp* pointer) {  pointer->~Tp();  // 调用dtor ~T()}// 第二个版本,接受两个迭代器,函数设法找出元素类别,进而利用 __type_traits<>使用最佳措施template inline void destroy(ForwardIterator first, ForwardIterator last) {  __destroy(first, last, VALUE_TYPE(first));}

针对 construct和destroy函数的泛化和特化版本示意图如下:

3. 空间的配置与释放,std::alloc

第二小节讲述了对象构造和对象析构的行为,现在来看看内存的配置和释放。在SGI版本的STL中,空间的配置和释放都由< stl_alloc.h > 负责。它的设计思想如下:

  • 向system heap要求空间
  • 考虑多线程(multi-thread)状态
  • 考虑内存不足的应变措施
  • 考虑过多“小型区块”可能导致的内存碎片问题(fragment)

SGI采用malloc()和free()完成内存的配置与释放,考虑到小型区块所可能造成的内存碎片的问题,SGI设计了双层配置器,第一级配置器直接使用malloc()和free();第二级配置器则视情况采用不同的策略:当配置区块超过128bytes时,调用第一级配置器;当配置区块小于128bytes时,为了降低额外负担,便采用复杂的memory pool整理方式,而不再求助第一级配置。STL规定了 __USE_MALLOC 宏,如果它存在则直接调用第一级配置器,不然则直接调用第二级配置器。SGI STL未定义该宏,也就是说默认使用第二级配置器。

第一和第二级配置器的关系如下:

从上述图可以看出,SGI STL提供了一层更高级的封装,定义了一个simple _ alloc类,无论是用哪一级都以模板参数alloc传给simple _ alloc,这样对外体现都是只是simple _ alloc:

templateclass simple_alloc {public:    static _Tp* allocate(size_t __n)      { return 0 == __n ? 0 : (_Tp*) _Alloc::allocate(__n * sizeof (_Tp)); }    static _Tp* allocate(void)      { return (_Tp*) _Alloc::allocate(sizeof (_Tp)); }    static void deallocate(_Tp* __p, size_t __n)      { if (0 != __n) _Alloc::deallocate(__p, __n * sizeof (_Tp)); }    static void deallocate(_Tp* __p)      { _Alloc::deallocate(__p, sizeof (_Tp)); }};

第一级配置器:直接调用malloc和free来配置释放内存,简单明了。

//㆒般而言是 thread-safe,并且对于空间的运用比较高效(efficient)。//以下是第一级配置器。//注意,无「template型别参数」。至于「非型别参数」inst,完全没派上用场。template class __malloc_alloc_template {private:// 函数指针,处理内存不足情况  static void* _S_oom_malloc(size_t);  static void* _S_oom_realloc(void*, size_t);#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG  static void (* __malloc_alloc_oom_handler)();#endifpublic:  static void* allocate(size_t __n)  {    // 第一级配置器直接使用malloc    void* __result = malloc(__n);    // 无法满足需求时,改用oom方法    if (0 == __result) __result = _S_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)  {    // 第一级配置器直接使用realloc()    void* __result = realloc(__p, __new_sz);    // 无法满足需求时使用oom_realloc()    if (0 == __result) __result = _S_oom_realloc(__p, __new_sz);    return __result;  }  // 以下模拟c++的set_new_handler(),可以指定自己的oom handler  static void (* __set_malloc_handler(void (*__f)()))()  {    void (* __old)() = __malloc_alloc_oom_handler;    __malloc_alloc_oom_handler = __f;    return(__old);  }};

第二级配置器:根据情况来判定,如果配置区块大于128bytes,说明“足够大”,调用第一级配置器,而小于等于128bytes,则采用复杂内存池(memory pool)来管理。第二级空间配置器的过程,重点关注allocate和deallocate这两个函数的具体实现。

static void* allocate(size_t n){    if (n > (size_t)__MAX_BYTES) // 字节数大于128,调用一级空间配置器        return malloc_alloc::allocate(n);    //不然到freelist去找    Obj* volatile* myFreeList = FreeList + FreeListIndex(n); //定位下标    Obj* ret = *myFreeList;    if (ret == NULL)    {        void* r = refill(Round_UP(n));//没有可用free list 准备装填    }    *myFreeList = ret->freeListLink;    return ret; }

可以看出allocate的处理逻辑为:

  • 如果用户需要的区块大于128,则直接调用第一级空间配置器
  • 如果用户需要的区块小于等于128,则到自由链表中去找如果自由链表有,则直接去取走不然则需要装填自由链表(refill)
static void deallocate(void* p, size_t n){    if (n > (size_t)__MAX_BYTES) //区块大于128, 则直接由第一级空间配置器收回        malloc_alloc::deallocate(p, n);    Obj* volatile* myFreeList = FreeList + FreeListIndex(n);    Obj* q = (Obj*)p;    q->freeListLink = *myFreeList;    *myFreeList = q;}

释放操作deallocate和上面处理类似:

  • 如果区块大于128, 则直接由第一级空间配置器收回
  • 如果区块小于等于128, 则有自由链表收回

我们在上面重点分析了整体思路,也就是二级配置器如何配置和申请内存,他们和一级配置器一样都提供allocate和deallocate的接口(其实还有个reallocate也是用于分配内存,类似于C语言中realloc函数),我们都提到了一点自由链表,那么自由链表是个什么?

如上图所示,自由链表是一个指针数组,有点类似与hash桶,它的数组大小为16,每个数组元素代表所挂的区块大小,比如free _ list[0]代表下面挂的是8bytes的区块,free _ list[1]代表下面挂的是16bytes的区块…….依次类推,直到free _ list[15]代表下面挂的是128bytes的区块。同时,我们还有一个被称为内存池地方,以start _ free和 end _ free记录其大小,用于保存未被挂在自由链表的区块,它和自由链表构成了伙伴系统。

我们之前讲了,如果用户申请小于等于128的区块,就到自由链表中取,但是如果自由链表对应的位置没了怎么办???这下子就轮到我们的内存池就发挥作用了!

下面重点讲述一下,如果自由链表对应的位置没有所需的内存块,那么应该如何处理,即refill函数的实现。

static void* allocate(size_t n){//...    if (ret == NULL)    {        void* r = refill(Round_UP(n));//没有可用free list 准备装填    }//...}
//freelist没有可用区块,将要填充,此时新的空间取自内存池static void* refill(size_t n){    size_t nobjs = 20;    char* chunk = (char*)ChunkAlloc(n, nobjs); //默认获得20的新节点,但是也可能小于20,可能会改变nobjs    if (nobjs == 1) //如果只有一块直接返回调用者,此时freelist无结点        return chunk;    //有多块,返回一块给调用者,其他挂在自由链表中    Obj* ret = (Obj*)chunk;    Obj* cur = (Obj*)(chunk + n);    Obj* next = cur;    Obj* volatile *myFreeList = FreeList + FreeListIndex(n);    *myFreeList = cur;    for (size_t i = 1; i < nobjs; ++i)    {               next = (Obj*)((char*)cur + n);        cur->freeListLink = next;        cur = next;    }    cur->freeListLink = NULL;    return ret;}

这里面的重点函数为ChunkAlloc,它的逻辑相对复杂,代码如下:

static char* ChunkAlloc(size_t size, size_t& nobjs){    size_t bytesLeft = endFree - startFree; //内存池剩余空间    size_t totalBytes = size * nobjs;    char* ret = NULL;    if (bytesLeft >= totalBytes) // 内存池大小足够分配nobjs个对象大小    {        ret = startFree;        startFree += totalBytes;        return ret;    }    else if (bytesLeft >= size) // 内存池大小不够分配nobjs,但是至少分配一个    {        size_t nobjs = bytesLeft / size;        totalBytes = size * nobjs;        ret = startFree;        startFree += totalBytes;        return ret;    }    else // 内存池一个都分配不了    {        //让内存池剩余的那么点挂在freelist上        if (bytesLeft > 0)        {            size_t index = FreeListIndex(bytesLeft);            ((Obj*)startFree)->freeListLink = FreeList[index];            FreeList[index] = (Obj*)startFree;        }        size_t bytesToGet = 2 * totalBytes + RoundUP(heapSize >> 4);        startFree = (char*)malloc(bytesToGet);        if (startFree == NULL)        {            //申请失败,此时试着在自由链表中找            for (size_t i = size; i <= __MAX_BYTES; i += __ALIGN)            {                size_t index = FreeListIndex(i);                Obj* volatile* myFreeList = FreeList + index;                Obj* p = *myFreeList;                if (FreeList[index] != NULL)                {                    FreeList[index] = p->freeListLink;                    startFree = (char*)p;                    endFree = startFree + i;                    return ChunkAlloc(size, nobjs);                }            }            endFree = NULL;            //试着调用一级空间配置器            startFree = (char*)MallocAlloc::Allocate(bytesToGet);        }        heapSize += bytesToGet;        endFree = startFree + bytesToGet;        return ChunkAlloc(size, nobjs);    }}

分析上述代码,我们知道当自由链表中没有对应的内存块,系统会执行以下策略:

如果用户需要是一块n字节的区块,且n <= 128(调用第二级配置器),此时refill填充是这样的:(需要注意的是:系统会自动将n字节扩展到8的倍数也就是Round_UP(n),再将Round_UP(n)传给refill)用户需要n块,且自由链表中没有,因此系统会向内存池申请nobjs * n大小的内存块,默认nobjs=20

  • 如果内存池大于 nobjs * n,那么直接从内存池中取出
  • 如果内存池小于nobjs * n,但是比一块大小n要大,那么此时将内存最大可分配的块数给自由链表,并且更新nobjs为最大分配块数x (x < nobjs)
  • 如果内存池连一个区块的大小n都无法提供,那么首先先将内存池残余的零头给挂在自由链表上,然后向系统heap申请空间,申请成功则返回,申请失败则到自己的自由链表中看看还有没有可用区块返回,如果连自由链表都没了最后会调用一级配置器.

这就是ChunkAlloc所执行的操作,在执行完ChunkAlloc函数后会获得内存(失败就抛出异常),此时也就是这段代码:

if (nobjs == 1) //如果只有一块直接返回调用者,此时freelist无结点        return chunk;    //有多块,返回一块给调用者,其他挂在自由链表中    Obj* ret = (Obj*)chunk;    Obj* cur = (Obj*)(chunk + n);    Obj* next = cur;    Obj* volatile *myFreeList = FreeList + FreeListIndex(n);    *myFreeList = cur;    for (size_t i = 1; i < nobjs; ++i)    {               next = (Obj*)((char*)cur + n);        cur->freeListLink = next;        cur = next;    }    cur->freeListLink = NULL;

如果只有一块返回给调用者,有多块,返回给调用者一块,剩下的挂在对应的位置。

4. 内存基本处理工具

STL定义有五个全局函数,这五个函数分别是construct(),destory(),uninitialized_copy(),uninitialized_fill(),uninitialized_fill_n(),都作用与未初始化的内存空间上。construct()和destory()上述章节已经讲解过,不再赘述。现在让我们了解下另外三个函数:uninitialized_copy(),uninitialized_fill(),uninitialized_fill_n(),分别对应于高层次函数copy()、fill()、fill_n()——这些都是STL的算法。它们都能够将内存的配置和对象的构造行为分离开来。

  • uninitialized_copy() 首先会在内存中分配一片内存(未构造),然后接受输入端的开始迭代器和输入端结束迭代器,对此输入的对象进行copy,然后放进分配的未构造的内存中。
template inline ForwardIterator uninitialized_copy(InputIterator first, InputIterator last, ForwardIterator result){return _uninitialized_copy(first, last, result, value_type(result));//以上利用value_type()取出first的value_type}template inline ForwardIterator _uninitialized_copy(InputIterator first, InputIterator last, ForwardIterator result, T*){typedef typename _type_traits::is_POD_type is_POD;return _uninitialized_copy_aux(first,last,result,is_POD());}//如果是POD型别,执行流程就会转到以下函数template inline ForwardIterator _uninitialized_copy_aux(InputIterator first, InputIterator last, ForwardIterator result, _true_type){return copy(first,last,result); //调用STL算法copy,效率高}//如果不是POD型别,执行流程就会转到以下函数templateForwardIterator _uninitialized_copy_aux(InputIterator first, InputIterator last, ForwardIterator result, _false_type){ForwardIterator cur = result;for (; first != last; ++first, ++cur)construct(&*cur,*first);  //必须一个一个元素构造,无法批量处理return cur;}

这个函数的逻辑是,首先萃取出迭代器result的value type,然后判断该型别是否为POD型别。POD意指Plain Old Data,也就是标量型别(scalar types)或传统的C struct型别。POD型别必然拥有 trivial ctor/dtor/copy/assignment函数,因此我们可以对POD型别采用最有效率的复制手法(采用STL的copy函数),而对non-POD型别采用最保险安全的做法(采用construct函数,一个个构造)。

  • uninitialized_fill()接受输入端的开始迭代器和输入端的结束迭代器(此范围内的迭代器指向未构造的内存),然后接受一个对象,把对象copy到此迭代器指向的未初始化的内存,也就是它会将指定范围内的未构造的内存指定相同的对象。
template inline void uninitialized_fill(ForwardIterator first,ForwardIterator last,const T& x){    _uninitialized_fill(first, last, x, value_type(first));//以上利用 value_type()取出first的 value_type}template inline void _uninitialized_fill(ForwardIterator first, ForwardIterator last, const T& x, T1*){typedef typename _type_traits::is_POD_type is_POD;_uninitialized_fill_aux(first, last, x, is_POD());}//如果是POD型别,执行流程就会转到以下函数template inline void _uninitialized_fill_aux(ForwardIterator first, ForwardIterator last, const T& x, _true_type){fill(first, last, x);  //调用STL算法fill}//如果不是POD型别,执行流程就会转到以下函数template inline void _uninitialized_fill_aux(ForwardIterator first, ForwardIterator last, const T& x, _false_type){ForwardIterator cur = first;for (; cur!=last;++cur)construct(&*cur, x); //必须一个一个元素构造,无法批量处理}

这个函数的逻辑是,与上述uninitialized_fill处理类似对POD型别采用最有效率的复制手法(采用STL的fill函数),而对non-POD型别采用最保险安全的做法(采用construct函数,一个个构造)。

  • uniniyialized_fill_n()接受输入端的开始处的迭代器(此分配的内存是未构造的)和初始化空间的大小,最重要的是它会给指定范围内的未构造的内存指定相同的对象。
template inline ForwardIterator uninitialized_fill_n(ForwardIterator first, Size n, const T& x){return uninitialized_fill_n(first,n,x,value_type(first));//以上利用 value_type()取出first的 value_type}template inline ForwardIterator uninitialized_fill_n(ForwardIterator first, Size n, const T& x, T1*){typedef typename _type_traits::is_POD_type is_POD;return uninitialized_fill_n_aux(first, n, x, is_POD());}//如果是POD型别,执行流程就会转到以下函数template inline ForwardIterator uninitialized_fill_n_aux(ForwardIterator first, Size n, const T& x, _true_type){return fill_n(first,n,x); //调用STL算法fill_n}//如果不是POD型别,执行流程就会转到以下函数template ForwardIterator uninitialized_fill_n_aux(ForwardIterator first, Size n, const T& x, _false_type){ForwardIterator cur = first;for (; n > 0; --n, ++cur)construct(&*cur,x); //必须一个一个元素构造,无法批量处理return cur;}

这个函数的逻辑是,与上述uninitialized_fill处理类似对POD型别采用最有效率的复制手法(采用STL的fill_n函数),而对non-POD型别采用最保险安全的做法(采用construct函数,一个个构造)。

总结:

uninitialized_copy(),uninitialized_fill(),uninitialized_fill_n()都会根据迭代器的value type,判断其是否为POD型别:1.为POD型别的对象提供高阶函数以提高处理效率;2:为non-POD型别提供construct函数以保证构造过程。

stl源码剖析_《STL源码剖析》学习笔记——空间配置器相关推荐

  1. STL空间配置器(二)

    ***上一篇是对STL空间配置器的入门级理解,在这一篇中,我将讨论更加深入的SGI STL空间适配器的内容.在下一节中,我将根据自己的理解,结合STL标准接口,实现一个符合STL标准的具有次级配置能力 ...

  2. STL源码剖析——空间配置器

    目录 构造和析构基本工具:construct() 和 destroy() 空间的配置与释放:std::alloc 二级空间配置器简述 空间配置函数allocate() 空间释放函数deallocate ...

  3. STL源码剖析学习二:空间配置器(allocator)

    STL源码剖析学习二:空间配置器(allocator) 标准接口: vlaue_type pointer const_pointer reference const_reference size_ty ...

  4. STL源码剖析 空间配置器 查漏补缺

    ptrdiff_t含义 减去两个指针的结果的带符号整数类型 ptrdiff_t (Type support) - C 中文开发手册 - 开发者手册 - 云+社区 - 腾讯云 std::set_new_ ...

  5. STL源码剖析(第二章 空间配置器)

    1.1 空间配置器 以STL的实现角度而言,第一个需要介绍的就是空间配置器,因为整个STL的操作对象(所有的数值)都存放在容器之内,而容器一定需要配置空间以置放资料.并且allocator称之为&qu ...

  6. SGI STL源码剖析——空间配置器

    SGI STL源码剖析--空间配置器 前言 空间配置器 SGI空间配置器 内存配置和对象构造 构造和析构 空间的配置和释放 第一级配置器 第二级配置器 空间配置 重新填充 重中之重的内存池 前言    ...

  7. 【《STL源码剖析》提炼总结】 第1节:空间配置器 allocator

    文章目录 一. 什么是空间配置器 二. STL allocator的四个操作: allocate,deallocate,construct,destroy `construct()` `destroy ...

  8. STL源码剖析---空间配置器

    看过STL空间配置器的源码,总结一下:       1.STL空间配置器:主要分三个文件实现,stl_construct.h  这里定义了全局函数construct()和destroy(),负责对象的 ...

  9. 深度剖析SGI STL二级空间配置器内存池源码

    文章目录 一.SGI STL二级空间配置器重要成员解读 二. 二级空间配置器内存池的结构 三. 两个重要的函数 1. _S_round_up 2. _S_freelist_index 四. 内存池al ...

最新文章

  1. 用ASP.NET建立一个在线RSS新闻聚合器(3)
  2. 【DBMS 数据库管理系统】数据仓库特征 ( 特征一 : 面向主题组织数据 | 特征二 : 数据集成 | 特征三 : 数据不可更新 | 特征四 : 随时间不断变化 )
  3. 面试题整理19 矩阵Z字形扫描
  4. 1803无法升级到2004_微软向win10 1803以后版本推送新的Edge浏览器更新 安装后不再支持直接卸载...
  5. 一个http请求的整个流程
  6. HDU - 6982 J - Road Discount wqs二分 + 模型转换 + 优化
  7. 作者:宾军志(1976-),男,御数坊(北京)科技咨询有限公司联合创始人。...
  8. ActiveMq生产者流量控制(Producer Flow Control)
  9. winform与数据库同步更新
  10. 二叉树的基本操作(C)
  11. 彻底解决VS中找不到 Windows SDK 版本 8.1的错误
  12. 2.5.PHP7.1 狐教程-【数据类型】
  13. linux切换机器ssh命令,linux – 如何编写一个bash shell脚本来ssh到远程机器并更改用户并导出一个env变量并执行其他命令...
  14. 新句子:没有谁比我更懂XX,抓普也不行
  15. 拼命成为有能力为自己老年生活买单的人|独秀日记
  16. ibm服务器装群晖系统,【科技实验室】如何给工控机电子盘刷上黑群晖系统和群晖引导?如何超简单搭建NAS 超详细保姆级教程...
  17. java写入html文件乱码,java写入到html文件 打开是乱码
  18. Chia 云P图 全套解决方案
  19. Python能不能只选择合并一个excel当中指定的sheet 当中指定的列呢?
  20. 西储大学轴承数据小波变换

热门文章

  1. Linux系统入门学习:在Debian或Ubuntu上安装完整的内核源码
  2. Linux设备驱动之mmap设备操作
  3. BM2 链表内指定区间反转
  4. matlab中.P文件的使用说明
  5. 巧用 PHP 数组函数
  6. 02 oracle 创建用户和授权
  7. 《Adobe Illustrator CS5中文版经典教程》—第0课0.5节使用绘图模式
  8. Python自动化运维工具-Fabric部署及使用总结
  9. 编码时的一些普适原则
  10. js判断fck编辑器内容是否为空并获得焦点