C++ STL(第三篇:空间配置器)

1、概述

以STL运用的角度而言,空间配置器是最不需要介绍的,它总是藏在一切组件的背后,默默工作。整个STL的操作对象都存放在容器之中(vertor、list),而容器一定需要配置空间以放置资料,这就是空间配置器的作用

虽然STL提供了让我们自定义空间配置器的接口,但是不建议自己定义,因为标准提供的空间配置器是安全的,且效率也不错的。所以我们使用时,一般都会使用默认的配置器。如下:

template  >
class vector {};

vect vec;	//这里只传入int类型,使用默认的空间配置器

下面的空间配置器是按照SGI 版本的STL进行讲解的,但是STL的原理是通的。

2、空间配置器的内存分配和释放

通过前面整理C++ new和delete的详解,我们知道C++内存配置操作和释放操作是这样的:

class Foo {...};
Foo* pf = new Foo;	//配置内存,然后构造对象
delete pf;			//将对象析构,然后释放内存

这其中的 new 内含两个阶段操作:1、调用operator new 配置内存。2、调用构造函数,构造对象内容
delete也内含两个阶段操作:1、调用析构函数。2、调用operator delete 释放内存。

为了精密分工,STL 将这两个阶段操作区分开来。内存配置操作由 成员函数 alloccate() 负责,内存释放由 deallcate() 负责;对象构造由 construct() 负责,对象析构则由 destroy() 负责

在内存分配的过程中,会有几个问题需要考虑。
1、小块内存带来的内存碎片问题。
2、小块内存频繁申请释放带来的性能问题。

为了解决这些问题,SGI STL设计了 双层级配置器,也就是第一级配置器和第二级配置器。第一级配置器直接使用 malloc() 和 free() ,第二级配置器则视情况采用不同的策略:当配置区块超过128 bytes 时,视之为 “足够大”,便调用第一级配置器;当配置区块小于 128 bytes 时,视之为 “过小” ,为了降低额外负担,便采用复杂的 内存池 管理方式。

3、第一级配置器

第一级配置器的流程如下:

C++ STL(第三篇:空间配置器)_第1张图片
SGI的第一级配置器以 malloc(), free(), realloc() 等C函数执行实际的内存配置、释放、重配置操作。当 malloc 或者 realloc 调用不成功后,改调用 oom_malloc() 和 oom_realloc() 。后两者都有内循环,不断调用“内存不足处理例程”,期望在某次调用之后,获得足够的内存。但如果“内存不足处理例程”未被客户端设定,则直接抛出 bad_alloc 异常,或者终止程序。

注意:设计内存不足处理例程是客户端的责任,设定内存不足处理例程也是客户端的责任

4、第二级配置器

二级配置器使用内存池+自由链表的形式避免了小块内存带来的碎片化,提高了分配的效率,提高了利用率。它是用一个16个元素的自由链表(free_list)来管理的,每个位置的内存大小都是8的倍数,分别为:8、16、24、32、40、48、56、64、72、80、88、96、104、112、120、128。

free_list的节点结构如下:

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

使用union是为了节省内存,这样每个节点就不需要额外的指针。

内存池与自由数组 free_list 之间的关系如下图所示:
C++ STL(第三篇:空间配置器)_第2张图片其中free_list的第一个元素指向 8个字节的空间,8个字节的空间我给分配了10个。free_list的最后一个元素指向128个字节的空间,此空间我给分配了4个。

free_list管理的是内存池中已经分配给 free_list 且尚未使用的内存,如果系统想从free_list中拿一8字节内存,则直接从free_list[0]中弹出顶部第一个元素,然后顶部后移。

4.1、二级配置器内存分配

主要分为四种情况:
1、free_list列表中有空余内存。如果申请3个字节的内存,则所需空间大小提升为8的倍数,然后去 free_list 中查找相应的链表,如果 free_list[i] 不为空,则返回第一个元素,然后把头指针往后移。

2、free_list 列表中没有空余,但内存池不为空。首先检验内存池中的大小是不是比申请的内存大,比如申请20*8的内存,如果足够,则分配相应内存,将其中一个分配给用户使用,其它的挂在相应的 free_list 中。如果内存池不够大,只够几个内存分配,则就分配这几个,把相应的数据返回。如果连一个都不够则执行第三中情况。

3、free_list列表中没有空余,内存池也不够。调用malloc重新分配内存,分配时会多分配一倍的内存,把相应的内存挂到free_list下,剩余的放到内存池中。

4、free_list列表中没有空余,内存池也不够,malloc也失败。则调用一级空间配置器,里面会有循环处理,或者抛出异常。

4.2、二级配置器内存回收

当用户从二级空间配置器中申请的内存被释放时,二级空间配置器将回收的内存插入到对应的 free_list 中。其流程如下:
C++ STL(第三篇:空间配置器)_第3张图片

4.4、总结

我们知道,引入相对复杂的空间配置器,主要源自两点:

1、频繁使用malloc、free开辟释放小块内存带来的性能效率的低下
2、内存碎片问题,导致不连续内存不可用的浪费

引入两层配置器帮我们解决了以上的问题,但是也带来一些问题:

1、内存碎片的问题,自由链表所挂区块都是8的整数倍,因此当我们需要非8倍数的区块,往往会导致浪费。
2、我们并没有释放内存池中的区块。释放需要到程序结束之后。这样子会导致自由链表一直占用内存,其它进程使用不了。

感谢大家,我是假装很努力的YoungYangD(小羊)

参考资料:
《STL源码剖析》

你可能感兴趣的:(STL,STL,空间配置器)