看过STL空间配置器的源码,总结一下:
      1、STL空间配置器:主要分三个文件实现,stl_construct.h  这里定义了全局函数construct()和destroy(),负责对象的构造和析构。stl_alloc.h文件中定义了一、二两级配置器,彼此合作,配置器名为alloc. stl_uninitialized.h 这里定义了一些全局函数,用来填充(fill)或复制(copy)大块内存数据,他们也都隶属于STL标准规划。
      在stl_alloc.h中定义了两级配置器,主要思想是申请大块内存池,小块内存直接从内存池中申请,当不够用时再申请新的内存池,还有就是大块内存直接申请。当申请空间大于128字节时调用第一级配置器,第一级配置器没有用operator::new和operator::delete来申请空间,而是直接调用malloc/free和realloc,并且实现了类似c++中new-handler的机制。所谓c++ new handler机制是,你可以要求系统在内存配置需求无法被满足时,调用一个指定的函数。换句话说,一旦::operator::new无法完成任务,在丢出std::bad_alloc异常状态之前,会先调用由客端指定的处理例程,该处理例程通常称为new-handler.new-handler解决内存做法有特定的模式。SGI第一级配置器的allocate()和realloc都是在调用malloc和realloc不成功后,改调用oom_malloc()和oom_realloc(),后两者都有内循环,不断调用"内存不足处理例程",期望在某次调用之后,获得足够的内存而圆满完成任务。但如果“内存不足处理例程“并未被客端设定,oom_malloc()和oom_realloc便调用_THROW_BAD_ALLOC, 丢出bad_alloc异常信息,或利用exit(1)硬生生中止程序。
     在stl_alloc.h中定义的第二级配置器中,如果区块够大,超过128字节时,就移交给第一级配置器处理。当区块小于128字节时,则以内存池管理,此法又称为次层配置,每次配置一大块内存,并维护对应的自由链表(free-list)。下次若再有相同大小的内存需求,就直接从free-list中拔出。如果客端释还小额区块,就由配置器回收到free-lists中,另外,配置器除了负责配置,也负责回收。为了管理方便,SGI第二级配置器会主动将任何小额区块的内存需求量上调至8的倍数。并维护16个free-lists,各自管理大小分别为8,16,24,32,40,48,56,64,72,80,88,96,104, 112,120,128 字节的小额区块。当申请小于等于128字节时就会检查对应的free list,如果free-list中有可用的区块,就直接拿来,如果没有,就准备为对应的free-list 重新填充空间。新的空间将取自内存池,缺省取得20个新节点,如果内存池不足(还足以一个以上的节点),就返回的相应的节点数.如果当内存池中连一个节点大小都不够时,就申请新的内存池,大小为2*total_bytes+ROUND_UP(heap_size>>4),totoal_bytes 为申请的空间大小,ROUND_UP调整为8的倍数,heap_size为当前总申请内存池的大小。如果申请该内存池成功就把原来内存池中剩下的空间分配给适当的free-list.万一山穷水尽,整个system heap空间都不够了(以至无法为内存池注入源头活水),malloc()行动失败,就会四处寻找有无"尚有未用区块,且区块足够大 "之free lists.找到了就挖一块交出,找不到就调用第一级配置器。第一级配置器其实也是使用malloc来配置内存。但它有out-of-memory处理机制(类似new-handler机制),或许有机会释放其他的内存拿来此处使用。如果可以就成功,否则发出bad_alloc异常。
      2、STL的默认内存分配器
      隐藏在这些容器后的内存管理工作是通过STL提供的一个默认的allocator实现的。当然,用户也可以定制自己的allocator,只要实现allocator模板所定义的接口方法即可,然后通过将自定义的allocator作为模板参数传递给STL容器,创建一个使用自定义allocator的STL容器对象,如:
    stl::vector<int, UserDefinedAllocator> array;
      大多数情况下,STL默认的allocator就已经足够了。这个allocator是一个由两级分配器构成的内存管理器,当申请的内存大小大于128byte时,就启动第一级分配器通过malloc直接向系统的堆空间分配,如果申请的内存大小小于128byte时,就启动第二级分配器,从一个预先分配好的内存池中取一块内存交付给用户,这个内存池由16个不同大小(8的倍数,8~128byte)的空闲列表组成,allocator会根据申请内存的大小(将这个大小round up成8的倍数)从对应的空闲块列表取表头块给用户。
这种做法有两个优点:
     (1)小对象的快速分配。小对象是从内存池分配的,这个内存池是系统调用一次malloc分配一块足够大的区域给程序备用,当内存池耗尽时再向系统申请一块新的区域,整个过程类似于批发和零售,起先是由allocator向总经商批发一定量的货物,然后零售给用户,与每次都总经商要一个货物再零售给用户的过程相比,显然是快捷了。当然,这里的一个问题时,内存池会带来一些内存的浪费,比如当只需分配一个小对象时,为了这个小对象可能要申请一大块的内存池,但这个浪费还是值得的,况且这种情况在实际应用中也并不多见。
     (2)避免了内存碎片的生成。程序中的小对象的分配极易造成内存碎片,给操作系统的内存管理带来了很大压力,系统中碎片的增多不但会影响内存分配的速度,而且会极大地降低内存的利用率。以内存池组织小对象的内存,从系统的角度看,只是一大块内存池,看不到小对象内存的分配和释放。
实现时,allocator需要维护一个存储16个空闲块列表表头的数组free_list,数组元素i是一个指向块大小为8*(i+1)字节的空闲块列表的表头,一个指向内存池起始地址的指针start_free和一个指向结束地址的指针end_free。空闲块列表节点的结构如下:

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

这个结构可以看做是从一个内存块中抠出4个字节大小来,当这个内存块空闲时,它存储了下个空闲块,当这个内存块交付给用户时,它存储的时用户的数据。因此,allocator中的空闲块链表可以表示成:
    obj* free_list[16];
      3、分配算法

// 算法:allocate
// 输入:申请内存的大小size
// 输出:若分配成功,则返回一个内存的地址,否则返回NULL
{if(size 大于 128)启动第一级分配器直接调用malloc分配所需的内存并返回内存地址;else{将size向上round up成8的倍数并根据大小从free_list中取对应的表头free_list_headif(free_list_head 不为空){从该列表中取下第一个空闲块并调整free_list,返回free_list_head}else{调用refill算法建立空闲块列表并返回所需的内存地址}}
}// 算法:refill
// 输入:内存块的大小size
// 输出:建立空闲块链表并返回第一个可用的内存地址
{调用chunk_alloc算法分配若干个大小为size的连续内存区域并返回起始地址chunk和成功分配的块数nobjif(块数为1)直接返回 chunk;else{开始在chunk地址块中建立free_list根据size取free_list中对应的表头元素free_list_head 将free_list_head 指向chunk中偏移起始地址为size的地址处,即free_list_head = (obj*)(chunk+size)再将整个chunk中剩下的nobj-1个内存块串联起来构成一个空闲列表返回chunk,即chunk中第一个空闲的内存块}
}// 算法:chunk_alloc
// 输入:内存块的大小size,预分配的内存块数nobj(以引用传递)
// 输出:一块连续的内存区域的地址和该区域内可以容纳的内存块的块数
{计算总共所需的内存大小total_bytesif(内存池足以分配,即end_free-start_free >= total_bytes){则更新start_free返回旧的start_free}else if(内存池不够分配nobj个内存块,但至少可以分配一个){计算可以分配的内存块数并修改nobj更新start_free并返回原来的start_free}else     // 内存池连一个内存块都分配不了{先将内存池的内存块链入到对应的free_list中后调用malloc操作重新分配内存池,大小为2倍的total_bytes为附加量,start_free指向返回的内存地址if(分配不成功){if(16个空闲列表中尚有空闲块)尝试将16个空闲列表中空闲块回收到内存池中再调用chunk_alloc(size,nobj)else调用第一级分配器尝试out of memory机制是否还有用}更新end_free为start_free+total_bytes,heap_size为2倍的total_bytes调用chunk_alloc(size,nobj)}
}// 算法:deallocate
// 输入:需要释放的内存块地址p和大小size
{if(size 大于128字节)直接调用free(p)释放else{将size向上取8的倍数,并据此获取对应的空闲列表表头指针free_list_head调整free_list_head将p链入空闲列表块中}
}

假设这样一个场景,free_list[2]已经指向了大小为24字节的空闲块链表,如图1所示,当用户向allocator申请21字节大小的内存块时,allocaotr会首先检查free_list[2]并将free_list[2]所指的内存块分配给用户,然后将表头指向下一个可用的空闲块,如图2所示。注意,当内存块在链表上是,前4个字节是用作指向下一个空闲块,当分配给用户时,它是一块普通的内存区。

                        图1 某时刻allocator的状态

                                  图2 分配24字节大小的内存块
4、小结
STL中的内存分配器实际上是基于空闲列表(free list)的分配策略,最主要的特点是通过组织16个空闲列表,对小对象的分配做了优化。
1)小对象的快速分配和释放。当一次性预先分配好一块固定大小的内存池后,对小于128字节的小块内存分配和释放的操作只是一些基本的指针操作,相比于直接调用malloc/free,开销小。
2)避免内存碎片的产生。零乱的内存碎片不仅会浪费内存空间,而且会给OS的内存管理造成压力。
3)尽可能最大化内存的利用率。当内存池尚有的空闲区域不足以分配所需的大小时,分配算法会将其链入到对应的空闲列表中,然后会尝试从空闲列表中寻找是否有合适大小的区域,
但是,这种内存分配器局限于STL容器中使用,并不适合一个通用的内存分配。因为它要求在释放一个内存块时,必须提供这个内存块的大小,以便确定回收到哪个free list中,而STL容器是知道它所需分配的对象大小的,比如上述:
    stl::vector<int> array;
array是知道它需要分配的对象大小为sizeof(int)。一个通用的内存分配器是不需要知道待释放内存的大小的,类似于free(p)。

STL源码剖析---空间配置器相关推荐

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

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

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

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

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

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

  4. STL 源码剖析 空间配置器

    以STL的运用角度而言,空间配置器是最不需要介绍的东西,它总是隐藏在一切组件(更具体地说是指容器,container) 的背后 但是STL的操作对象都存放在容器的内部,容器离不开内存空间的分配 为什么 ...

  5. [STL源码剖析]空间置配器allocator

    空间置配器allocator placement new 与 new new关键字操作: 调用operator new为对象申请内存 在申请到的内存地址上调用对象的构造函数 返回对象的地址 place ...

  6. STL源码剖析 配接器

    配接器(adapters)在 STL组件的灵活组合运用功能上,扮演着轴承.转换器的角色.Adapter这个概念,事实上是一种设计模式(design pattern). «Design Patterns ...

  7. 《STL源码剖析》相关面试题总结

    一.STL简介 STL提供六大组件,彼此可以组合套用: 容器 容器就是各种数据结构,我就不多说,看看下面这张图回忆一下就好了,从实现角度看,STL容器是一种class template. 算法 各种常 ...

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

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

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

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

最新文章

  1. 推荐几个MySQL大牛的博客
  2. (转载)hive文件存储格式
  3. 第一个Struts2程序
  4. 0.3秒定位解剖位置、定位精度提升超2.3%!
  5. [Qt教程] 第28篇 XML(二)使用DOM创建和操作XML文档
  6. 【NOI2007】货币兑换【任意坐标斜率优化】【CDQ分治】
  7. php psl标准,psl是什么单位
  8. 绝对零门槛,IDEA两步搭建好Java开发环境
  9. mysql查到库怎么进入表_mysql如何进入数据库查看所有表
  10. iOS URL Scheme 劫持-在未越狱的 iPhone 6上盗取支付宝和微信支付的帐号密码
  11. java short 写法_Java Short类shortValue()方法及示例
  12. 10003 微信登录失败 redirect_uri域名与后台配置不一致
  13. layui数据表格与后台交互进行渲染
  14. 简单介绍pytorch中分布式训练DDP使用 (结合实例,快速入门)
  15. 网络调试助手TCP server不能成功连接
  16. 图像特征提取与匹配——SIFT算法
  17. 解决QGIS中GEE插件授权问题
  18. 孙悟空java游戏棍子可以升级_上古有四只神猴,各有一把神器棍子,孙悟空的金箍棒是其中之一!...
  19. FTP登录时一直弹出登录窗口,就算输入正确的账号密码也不能登录
  20. 推荐16个免费开源的商业智能工具

热门文章

  1. 超长的十六进制数转换为八进制
  2. c#怎么调用oracle存储过程,c# 调用oracle 存储过程、方法简易例子
  3. 第三届Apache Flink 极客挑战赛暨AAIG CUP攻略发布!
  4. 开放报名 | 阿里云 Serverless 研修班走进四川大学
  5. qt 操作html,如何在Webkit窗口中操作页面内容(使用QT和QTWebKit)?
  6. 在unity2d同屏显示9千人
  7. PHP面试题:对于用户输入一串字符串$string,要求$string中只能包含大于0的数字和英文逗号,请用正则 表达式验证,对于不符合要求的$string返回出错信息
  8. Oracle中的Raw类型解释
  9. java工程师面试如何自我介绍
  10. 人生影响最大的三位老师