STL作为C++的经典作品,一直备受人们关注。本文主要介绍STL的内存管理策略。

早期的STL内存管理

第一次接触STL源码是看侯捷先生的《STL源码剖析》,此书通俗易懂,剖析透彻,是极佳的STL分析教程。不过由于是在2002年出版的,所以内容有些陈旧,不过仍然具有参考价值。

现代g++的STL是由SGI版的STL演化而来。

正如侯捷先生书中所讲,早期STL内存分配有两种方法:malloc/realloc/free和内存池。默认的内存分配策略是内存池,下图中代码节选自stl_alloc.h头文件:

由图中代码(代码摘自可知SGI3.3版本的STL源码),如果需要分配的内存容量大于128字节时便会转而调用malloc/realloc/free版的内存分配方法。如果需要分配的内存大小小

于128字节便会调用内存池来满足需求,这种策略增加了代码的复杂性,但减少了内存碎片的问题。

malloc/realloc/free的方法

这种分配方法定义在stl_alloc.h文件的__malloc_alloc_template类模板中,它只是对malloc/realloc/free的简单封装,并增加了一些措施用于处理内存不足时的情况(不断尝试分配,

释放,再分配,再释放)。

内存池

在SGI3.3中,内存池就是用一个指针数组存储指向一个个不同的固定大小的已事先分配好的链表头节点的指针。这句话可能有点绕口,分开说就是:内存池的主体是一个指针数组,数组中存储的每个

指针都指向一个链表,每个链表的节点大小都是固定的,这些链表的每个节点都是事先分配好的内存,这些内存供客户取用。用图来表示就是如下图:

图中的链表的每个节点是分开的,这是为了易于理解,真实情况是每个节点都是紧挨着的,因为每次分配一大块内存(用malloc),然后按固定大小切分,并且用指针将它们连接起来。图中只画了7个

链表,真实情况是16个链表,各自管理着大小为8,16,24,32,40,48,56,64,72,80,88,96,104,112,128字节的区块。当需要的内存大小与这16个大小不匹配那么就向上舍入至最接近的大小(这会不

会造成更小的内存碎片?)。

有兴趣的可以看以下具体实现,在stl_alloc.h文件中的__default_alloc_template类中。从源码可以看出,内存池的实现比较复杂,特别是内存的分配和链表的构造。

现在的STL内存管理

现在的STL内存管理与早期的有很大不同,以g++5.4为例。从g++3.4开始,STL改变了原来的内存分配策略,默认的内存分配方式改为new/delete。在Linux上查看当前系统的STL内存分配源代码的

顺序为:bits/allocator.h -> bits/c++allocator.h -> ext/new_allocator.h。其中allocator.h是对底层内存分配器的封装,供其他STL组件直接调用;bits/c++allocator.h中只

有一行代码:

template<typename _Tp>
using __allocator_base = __gnu_cxx::new_allocator<_Tp>;

它定义了底层内存分配器为new_allocatorext/new_allocator.h是底层分配器的定义文件,它定义了new_allocator类,这个类只是对new/delete的简单封装。

由于容器每次需要内存时调用new,释放内存时调用delete,所以这种内存分配方法会比内存池要。但是其优势是在各种硬件和操作系统甚至大的集群中也能正确工作。另一种方法是使用

分配器中的缓存,这种额外的机制有多种实现形式:位图索引,一个以2的指数级成倍增加的桶;或者是一个简单的固定大小的缓冲池。分配器中的缓冲在一个程序的多个容器中共享。使用这些技术的

bitmap_allocatorpool_allocatormt_alloc。由于不同的实现,不同的操作系统和不同的编译环境,扩展缓存分配器可能会非常棘手。特别是,内存池创建和析构的顺序可能很难确

定,当和插件一起使用或者在内存中装载和卸载共享对象时可能会产生问题。

以上内容节选自gcc关于内存分配的手册页中的说明,由此可见,gcc放弃使用继承自sgi的内存池而使用new/delete是为了降低复杂度和增加可靠性。

gcc定义了几种内存分配方法:

  1. new_allocator

    new/delete的简单封装,也是各种顺序容器和关联容器的默认内存分配器。

  2. malloc_allocator

    malloc/free的简单封装,增加了对out-of-memory的处理。

  3. array_allocator

    允许使用通过构造std::array对象分配的现有全局或外部存储来分配已知和固定大小的内存。通过使用这个分配器,可以使用固定大小的容器(包括std::string),而无需调用newdelete

    此功能允许使用STL抽象,无需运行时复杂性或开销,即使在诸如程序启动的情况下。

  4. debug_allocator

    围绕任意分配器的包装器A.它将稍微增加大小的请求传递给A,并使用额外的内存来存储大小信息。 当指针传递给deallocate()时,检查存储的大小,并使用assert()来保证它们匹配。

  5. throw_allocator

    包括内存跟踪和标记能力以及在可配置的时间间隔抛出异常。

  6. __pool_alloc

    一个高性能,单池分配器。可重用的内存在这种类型的相同实例化之间共享。当它的内存用完时它调用通过运算符new获取新的内存。如果客户容器请求大于某个阈值大小的内存块,则绕过内存池,并且将分配/释放请求直接传递给new

  7. __mt_alloc

    一个高性能固定大小的分配器。

  8. bitmap_allocator

    一个高性能的分配器,使用位图来跟踪和标记使用和未使用的内存。

参考资料:

  1. 《STL源码剖析》侯捷 著 2002年

  2. gcc手册页

C++STL内存管理方法(g++版)的更多相关文章

  1. STL内存管理

    1. 概述 STL Allocator是STL的内存管理器,也是最低调的部分之一,你可能使用了3年stl,但却不知其为何物. STL标准如下介绍Allocator the STL includes s ...

  2. 现代JVM内存管理方法的发展历程,GC的实现及相关设计概述(转)

    JVM区域总体分两类,heap区和非heap区.heap区又分:Eden Space(伊甸园).Survivor Space(幸存者区).Tenured Gen(老年代-养老区). 非heap区又分: ...

  3. windows的三种内存管理方法

    Windows的内存管理方法 windows提供了3种方法来进行内存管理: l         虚拟内存,最适合用来管理大型对象或者结构数组 l         内存映射文件,最适合用来管理大型数据流 ...

  4. SGI STL内存管理

    前言 万丈高楼平地起,内存管理在C++领域里扮演着举足轻重的作用.对于SGI STL这么重量级的作品,当然少不了内存管理的实现.同时,想要从深层次理解SGI STL的原理,必须先将内存管理这部分的内容 ...

  5. STL内存管理器的分配策略

    STL提供了很多泛型容器,如vector,list和map.程序员在使用这些容器时只需关心何时往容器内塞对象,而不用关心如何管理内存,需要用多少内存,这些STL容器极大地方便了C++程序的编写.例如可 ...

  6. C语言知识整理(3):内存管理(详细版)

    在计算机系统,特别是嵌入式系统中,内存资源是非常有限的.尤其对于移动端开发者来说,硬件资源的限制使得其在程序设计中首要考虑的问题就是如何有效地管理内存资源.本文是作者在学习C语言内存管理的过程中做的一 ...

  7. Objective-C:内存管理

    1 传统内存管理 Objective-C对象的生命周期可以分为:创建.存在.消亡. 1.1 引用计数 类似Java,Objective-C采用引用计算(reference counting)技术来管理 ...

  8. ObjC如何通过runtime修改Ivar的内存管理方式

    ObjC如何通过runtime修改Ivar的内存管理方式 为什么要这么做? 在iOS 9之前,UITableView(或者更确切的说是 UIScrollView)有一个众所周知的问题: propert ...

  9. 【转帖】linux内存管理原理深入理解段式页式

    linux内存管理原理深入理解段式页式 https://blog.csdn.net/h674174380/article/details/75453750 其实一直没弄明白 linux 到底是 段页式 ...

随机推荐

  1. oracle之synonym小结

    oracle中的同义词可以认为是对表.视图.序列.存储过程.函数.程序包或者其他同义词的一个别名,也就是用一个别名来映射的作用. oracle中的同义词可以分为私有和公有两种,私有同义词(privat ...

  2. 1.ARM的基础知识

    ARM简述 ARM公司既不生产芯片也不销售芯片,它只出售芯片技术授权.ARM技术具有很高的性能和功效,因而容易被厂商接受.同时,合作伙伴的增多,可获得更多的第三方工具.制造和软件支持,这又会使整个系统 ...

  3. 功能强大的web打印控件lodop的使用

    打印是很多web系统都需要的功能,最近找到一款功能强大,使用简单,价格便宜的web打印工具Lodop,免费也能用,不过有水印,也不贵商业开发建议购买. 废话不多说,拿来就用,从简单的打印开始. 1.下 ...

  4. 四则运算GUI设计2.0

    使用QT设计的界面如下: 程序流程是点击开始出题,会在题目后面的框中显示所出的题目,在输入答案以后点击提交答案会判断输入的答案是否正确. 输入后的界面: 部分代码如下: qtyunsuan.h文件: ...

  5. Python基础篇【第8篇】: Socket编程(二)SocketServer

    SocketServer 在上一篇文章中我们学习了利用socket模块创建socket通信服务,但细心学习后就会发现利用socket模块创建的服务无法进行多进程的处理,当需要进行大量请求处理时,请求就 ...

  6. .NET MEF入门级例子

    学习新东西,喜欢从简单的例子入手,感觉理解和上手会快点,本文记录下我做的一个简单的mef的例子,至于理论的话百度,谷歌多的去了. Mef可以在你调整了某些功能的时候不需要重新去做代码,只需要换掉相应的 ...

  7. shape 代码生成器

    场景: 可以尝试使用shape的xml文件来代替图片. 可以起到减小包大小的效果. Android Button Maker是一个可以在线生成按钮代码的工具.Android API提供了XML文件定义 ...

  8. 【VB6】使用VB6创建和访问Dom树【爬虫基础知识 】

    使用VB6创建和访问Dom树 关键字:VB,DOM,HTML,爬虫,IHTMLDocument 我们知道,在VB中一般大家会用WebBrowser来获取和操作dom对象. 但是,有这样一种情形,却让我 ...

  9. java.lang.OutOfMemoryError: Java heap space

    java.lang.OutOfMemoryError: Java heap space 原因:内存溢出,内存一直申请一直占用,无法回收 解决方法:定时重启下服务,

  10. @override的意思

    @Override是Java5的元数据,自动加上去的一个标志,告诉你说下面这个方法是从父类/接口 继承过来的,需要你重写一次,这样就可以方便你阅读,也不怕会忘记 @Override是伪代码,表示重写( ...