c++ STL(六 空间配置器、内存配置器)

一、概述

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

虽然STL提供了让咱们自定义空间配置器的接口,可是不建议本身定义,由于标准提供的空间配置器是安全的,且效率也不错的。因此咱们使用时,通常都会使用默认的配置器。以下:数组

template <class T, class Alloc = allocator<T> >
class vector {};

vect<int> vec;	//这里只传入int类型,使用默认的空间配置器
  • 1
  • 2
  • 3
  • 4

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

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

经过前面整理C++ new和delete的详解,咱们知道C++内存配置操做和释放操做是这样的:函数

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

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

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

在内存分配的过程当中,会有几个问题须要考虑。
一、小块内存带来的内存碎片问题。
二、小块内存频繁申请释放带来的性能问题。.net

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

三、第一级配置器

第一级配置器的流程以下:指针

在这里插入图片描述
SGI的第一级配置器以 malloc(), free(), realloc() 等C函数执行实际的内存配置、释放、重配置操做。当 malloc 或者 realloc 调用不成功后,改调用 oom_malloc() 和 oom_realloc() 。后二者都有内循环,不断调用“内存不足处理例程”,指望在某次调用以后,得到足够的内存。但若是“内存不足处理例程”未被客户端设定,则直接抛出 bad_alloc 异常,或者终止程序。code

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

四、第二级配置器

二级配置器使用内存池+自由链表的形式避免了小块内存带来的碎片化,提升了分配的效率,提升了利用率。它是用一个16个元素的自由链表(free_list)来管理的,每一个位置的内存大小都是8的倍数,分别为:八、1六、2四、3二、40、4八、5六、6四、7二、80、8八、9六、10四、1十二、120、128。

free_list的节点结构以下:

union obj
{
	union obj* free_list_link;
	char client_data[1];
};
  • 1
  • 2
  • 3
  • 4
  • 5

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

内存池与自由数组 free_list 之间的关系以下图所示:
在这里插入图片描述其中free_list的第一个元素指向 8个字节的空间,8个字节的空间我给分配了10个。free_list的最后一个元素指向128个字节的空间,此空间我给分配了4个。

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

4.一、二级配置器内存分配

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

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

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

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

4.二、二级配置器内存回收

当用户从二级空间配置器中申请的内存被释放时,二级空间配置器将回收的内存插入到对应的 free_list 中。其流程以下:
在这里插入图片描述

4.四、总结

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

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

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

一、内存碎片的问题,自由链表所挂区块都是8的整数倍,所以当咱们须要非8倍数的区块,每每会致使浪费。
二、咱们并无释放内存池中的区块。释放须要到程序结束以后。这样子会致使自由链表一直占用内存,其它进程使用不了。

 

 

STL下的空间配置器分位两级,他们没有高低之分,只有一个条件,当用户所须要的的内存大小:

1.大于128字节时,交给一级配置器处理
2.小于等于128字节时,交给二级配置器处理
 
一级配置器的实现思想:实际就是对c下的malloc,relloc,free进行了封装,而且在其中加入了c++的异常。
咱们要了解对于当内存不足调用失败后,内存不足处理是用户须要解决的问题,STL不处理,只是在你没有内存不足处理方法或有可是调用失败后抛出异常。
 
 
二级配置器的实现思想:
二级配置器是经过内存池和空闲链表配合起来的一个特别精巧的思想。
空闲链表:它每一个节点分别维护8,16,32.。。。128字节以8的倍数的各内存大小。
它是这样作的,首先,用户申请内存小于128个字节,进入二级配置器程序,假如第一次用户申请内存为32字节,程序直接调用S_chunk_alloc,直接申请40个大小的32字节空间,一个交给客户,另外19个交给空闲链表,剩下的20个交给内存池。
接下来,第二次申请空间,假设此次用户须要64字节的空间,首先,程序检查64字节的空闲链表节点是否有空闲空间,若是有,直接分配,若是没有,就去内存池,而后检查内存池的大小能分配多少个本身大小的节点,彻底知足,则拿出一个给用户,剩下的19个给空闲链表,若是不够,尽量分配内存池的最大个数给用户和空闲链表。若是一个都分配不出,首先把内存池中的剩下没用的小内存分给相应适合的空闲链表,而后继续调用S_chunk_alloc申请内存,内存申请不足,去检索空闲链表有没有还没有使用的大的内存块,若是有,就拿出来给给内存池,递归调用S_chunk_alloc,若是空闲链表也没有了,则交给一级适配器(由于其有对内存不足处理的方法)。内存够,则拿到申请的内存补充内存池。重复上面的操做,将一个给客户,其余的19个交给空闲链表,剩下的给内存池。
 
二级配置器实现了对小内存的高效管理,使内部碎片出现的几率大大下降,我的看法,二级适配器差很少已经达到了对小内存接近完美的处理。
相关文章
相关标签/搜索