STL的学习

STL 是 Standard Template Library(标准模板库)的缩写。Standard 是指STL是C++标准程序库的一部分,Template是指STL是一套模板,这也是STL最本质的特征。标准模板库使得C++编程语言在有了同Java一样强大的类库的同时,保有了更大的可扩展性。

2. STL六大组件

2.1空间配置器(Memory Allocation)

负责空间配置与管理,本质是实现动态空间配置、空间管理、空间释放的一系列class template。它是容器的底层接口,实际使用STL的用户是看不到Allocation的。

2.2 迭代器(iterators)

迭代器扮演容器与算法之间的胶合剂,可以形象的理解为“泛型指针”。从实现的角度看,迭代器是一种将operator*、operator->、operator++、operator–等指针相关操作进行重载的class template。所有的STL容器都有自己专属的迭代器——是的,只有容器设计者才知道如何遍历自己的元素,原生指针(Native pointer)也是一迭代器。

2.3 容器(containers)

各种数据结构,可以看为存储数据的器皿。

  • 序列容器:vector、list、deque、string、array
  • 容器适配器:stack、queue(底层实现利用deque)
  • 关联容器:map、set、multi-map、multi-set

2.4 算法(algorithms)

STL算法是一种函数模板(function template),各种常见算法如:sort,search,copy,erase...

2.5 仿函数(functors)

函数对象的行为类似函数,但可作为算法的某种策略(policy)。从实现的角度看,函数对象是一种重载了operator()(函数调用操作符)的class或class template。

2.6 适配器(Adapters)

适配器是一种用来修饰容器或函数对象或迭代器的东西。例如,STL 提供queue和stack,虽然他们看似容器,但其实只能算是一种容器适配器,因为他们的底层实现完全借助于deque,所有的操作都由底层deque提供。改变functor/container/iterator的接口者称为functor/container/iterator adaptor。

2.7 关系结构图

3. 空间配置器

3.1 SGI STL默认空间配置器 

谈及空间配置器,首先说明一下SGI STL内部的两种结构std::allocator 和 std::alloc。为什么会有两种呢,在stl源码中默认使用哪个呢?

首先,对于std::allocator是SGI定义的标准、名为allocator的配置器,但是SGI自己并不使用它,主要因素为其效率不佳,仅仅是把C++中的operator new和operator delete做了一层简单的包装而已,并没有考虑到内存分配中的任何效率上的强化。

所以说,在SGI STL源码中采用的是后者,std::alloc即stl容器的缺省空间配置,下面我们讨论的内存分配理论体系也都是基于该方式。

3.2 C++内存配置和释放操作

我们知道,当使用new操作符建立一个对象时,内部进行了两个操作(1)配置内存(2)调用构造函数;同理,当使用delete删除一个对象时,内部依然进行了两个操作(1)析构对象(2)释放内存。

如下所示:

 
  1. class Foo{...};

  2.  
  3. Foo *pf = new Foo();

  4. delete pf;

这其中的new算式含两个阶段的操作:

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

(2)调用Foo::Foo()构造对象内容;

同理,delete算式也内涵两个阶段的操作:

(1)调用Foo::~Foo()将对象析构;

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

正是为了精密分工,SGI STL采用的std::alloc将这两个阶段操作分开进行。

  • 内存配置操作由alloc:allocate()负责,内存释放操作由alloc:deallocate()负责; -- 《stl_alloc.h》
  • 对象析构由::construct()负责,对象析构由::destroy()负责;-- 《stl_construct.h》

结构如下图所示:

3.3 内存配置与释放详解

对于对象的构造和析构,在这儿不多说,下面我们详细探析stl中的内存分配与释放策略。

对象构造前的内存分配和对象析构后的内存释放都是由stl_alloc.h文件实现的,该文件都涵盖了哪些因素呢?其设计哲学:

  • 向system heap申请空间;
  • 考虑多线程(multi-threads)状态;
  • 考虑内存不足时的应变;
  • 考虑过多“小型区块”带来的内存碎片问题;

对于,申请和释放空间步骤,C++的基本操作为::operator new()和::operator delete(),这两个全局函数相当于C的malloc()和free(),事实上也确实如此,SGI正是以malloc()和free()来完成内存的申请和释放的。

我们讨论的重点应该在于如何处理内存不足以及减少内存碎片呢?

(1)考虑到小型区块所可能造成的内存破碎问题,SGI设计了双层级配置器。
当配置区块超过128bytes时(视为足够大),调用第一层级配置器,第一层级配置器直接使用malloc()和free()。
当配置区块小于128bytes时(视为过小),调用第二层级配置器,采用复杂的memory pool整理方式,来降低额外开销。

注:第一级配置器以malloc()、free()、realloc()等C函数来执行实际的内存配置、释放、重新配置操作,并实现出类似C++ new handler机制,是的,它不能直接运用C++ new-handler机制,因为它并非引用::operator new来配置内存。

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

(2)二级配置器的内存池

内存池管理方式是每次配置一大块内存,并维护对应之自由链表。
若有相同大小的内存分配,就直接从自由链表中分配内存块;
若内存释放时,则由配置器回收到自由链表中;

第二级配置器会主动将任何小额区块的内存需求量调至8的倍数。

自由链表(free-list)节点的结构如下:

利用union的特性,从第一字段观之,obj可被视为一个指针,指向相同形式的另一个obj;
从第二字段观之,obj可被视为一个指针,指向实际区块;
一物二用,不会为了维护链表所必须的指针而造成内存的一种浪费。
 

第二级配置器分配内存时,其具体步骤如下:

  1. 判断内存块大小,是否大于128bytes,若大于,则调用第一级配置器.若小于,进行步骤2)
  2. 从16个自由链表中,根据内存块大小选择合适的自由链表
  3. 判断自由链表是否为空,若为空,则重新填充自由链表,否则,进行步骤4)
  4. 调整当前自由链表指向一块内存块,并返回当前的内存块.(类似于链表的删除操作)

注:当自由链表中没有可用的区块时,则调用refill()函数重新填充。新的空间取自内存池(经由chunk_alloc()完成)。缺省取得20个新节点(新区块),但是,万一内存池空间不足,获得的节点(区块)数可能少于20个。

第二级配置器释放内存时,其具体步骤如下:

  1. 判断内存块大小,是否大于128bytes,若大于,则调用第一级配置器.若小于,进行步骤2).
  2. 从16个自由链表中,根据内存块大小选择合适的自由链表.
  3. 调整当前自由链表回收当前的内存块.(类似于链表的插入操作)

内存池管理:

三个变量来标识内存池的使用情况和大小,start_free,end_free,heap_size。

步骤:

  1. 内存池剩余空间完全满足需求量(调出20个区块返回给free list),则直接修改start_free的值,返回对应的区块;
  2. 内存池剩余空间不能完全满足需求量,但足够供应一个(含)以上的区块,则减少分配的区块数目,然后分配;
  3. 内存池剩余空间连一个区块的大小都无法提供,则调用第一级配置器利用malloc增加内存空间;

举个例子:

如上图,假设程序一开始,客户端就调用chunk_alloc(32,20),于是malloc配置40个32bytes的区别,其中第一个交出,另19个交给free_list[3]管理,剩余20个留给内存池。接下来,客户端调用chunk_alloc(64,20),此时free_list[7]空空如也,必须向内存池要求支持。内存池此时只能供应(32*20)/64=10个,就把这10个区块返回,第一个交给客户端,剩余9个交给free_list[7]维护。

此时,内存池便全空,接下来再调用chunk_alloc(96,20)此时free_list[11]空空,向内存池要求支持,而内存池也是空的,所以调用malloc配置40+n个96bytes的区块,其中第一个交出,另19个交给free_list[11]维护,余20+n个区块留给内存池。

万一山穷水尽,整个system heap空间都不够,malloc调用失败,chunk_alloc()函数就四处寻找“尚有可用区块,且足够大”之free_lists。找到了就挖出一块交出,找不到则调用第一级空间配置器,也是调用malloc,它有out-of-memory处理机制(类似于C++ new-handler),或许有机会释放其它内存拿来使用。如果可以就成功,否则发出bad_alloc异常。

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/fly_yr/article/details/52091891

猜你喜欢

转载自blog.csdn.net/Mario_z/article/details/81585956