SGI设计了双层级配置器,第一级配置器直接使用malloc()和free(),第二级配置器则视情况采用不同的策略:当配置区块超过128bytes时,视之为“足够大”,便调用第一级配置器;当配置区小于128bytes时,视之为“过小”,为了降低额外负担,便采用复杂的memory pool 整理方式,而不再求助于第一级配置器。整个设计究竟只开放第一级配置器,取决于_USE_MALLOC是否被定义:

 #ifdef  __USE_MALLOC
...
typedef __malloc_alloc_template<> malloc_alloc ;
typedef malloc_alloc alloc ; //令alloc为第一级配置器
#else
//令alloc为第二级配置器
typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS,> alloc ;
#endif /*! __USE_MALLOC*/

SGI STL 第一级配置器

template<int inst>

class  __malloc_alloc_template {…} ;

注释:

1.allocate()直接使用malloc(),deallocate()直接使用free()。

2.模拟C++的set_new_handler()以处理内存不足的状况。第一级配置器以malloc(),free(),realloc()等C函数执行实际的内存配置、释放、重配置操作。

SGI STL 第二级配置器

template<bool threads,int inst>

class __default_alloc_template {…} ;

注释:

1.维护16个自由链表(free lists),负责16种小型区块的次配置能力。内存池(memory pool)以malloc()配置而得。如果内存不足,转调用第一级配置器。

2.如果需求区块大于128byte,就转调用第一级配置器。

下面是一些具体的理解分享:

default allocator

SGI STL 的头文件defalloc.h中有一个符合标准的名为allocator的内存分配器,它只是简单地将::operator new 和::operator delete做了一层薄薄的封装。在SGI STL的容器和算法部分从来没有用到这个内存分配器。

STL 的内存分配策略

首先简要介绍一下STL中对内存分配的规划

当用户用new构造一个对象的时候,其实内含两种操作:1)调用::operator new申请内存;2)调用该对象的构造函数构造此对象的内容

当用户用delete销毁一个对象时,其实内含两种操作:1)调用该对象的析构函数析构该对象的内容;2)调用::operator delete释放内存

SGI STL中对象的构造和析构由::construct()和::destroy()负责;内存的申请和释放由alloc:allocate()和alloc:deallocate()负责;此外,SGI STL还提供了一些全局函数,用来对大块内存数据进行操作。

上一段提到的三大模块分别由stl_construct.h, stl_alloc.h, stl_uninitialized.h 负责

对象的构造和析构工具(stl_construct.h)

stl_construct.h中提供了两种对象的构造方法,默认构造和赋值构造:

 template <class _T1, class _T2>
inline void _Construct(_T1* __p, const _T2& __value) {
new ((void*) __p) _T1(__value);
} template <class _T1>
inline void _Construct(_T1* __p) {
new ((void*) __p) _T1();
}

上面两个函数的作用是构造一个类型为T1的对象,并由作为参数的指针p返回。

其中的new (_p) _T1(_value); 中使用了placement new算子,它的作用是通过拷贝的方式在内存地址_p处构造一个_T1对象。(placement new能实现在指定的内存地址上用指定类型的构造函数来构造一个对象)。

在对象的销毁方面,stl_construct.h也提供了两种析构方法:

 template <class _Tp>
inline void _Destroy(_Tp* __pointer) {
__pointer->~_Tp();
} template <class _ForwardIterator>
inline void _Destroy(_ForwardIterator __first, _ForwardIterator __last) {
__destroy(__first, __last, __VALUE_TYPE(__first));
}

第一个版本的析构函数接受一个指针,将该指针所指的对象析构掉;第二个版本的析构函数接受first和last两个迭代器,将这两个迭代器范围内的对象析构掉。

在第二个版本的destroy函数里面,运用了STL中惯用的traits技法,traits会得到当前对象的一些特性,再根据特性的不同分别对不同特性的对象调用相应的方法。在stl_construct.h中的destroy中,STL会分析迭代器所指对象的has_trivial_destructor特性的类型(只有两种:true_type和false_type),如果是true_type,STL就什么都不做;如果是false_type,就会调用每个对象的析构函数来销毁这组对象。

除此之外,stl_construct还为一些基本类型的对象提供了特化版本的destroy函数,这些基本类型分别是char, int, float, double, long。当destroy的参数为这些基本类型时,destroy什么都不做。

内存空间管理工具alloc

我想以自底向下的顺序介绍一下STL的allocator。首先说说STL内建的两种分配器,然后介绍STL如何封装这两种分配器对外提供统一的接口,最后用一个vector的例子看看容器如何使用这个allocator。

1 __malloc_alloc_template内存分配器

该分配器是对malloc、realloc以及free的封装:

 static void* allocate(size_t __n)
{
void* __result = malloc(__n);
if ( == __result) __result = _S_oom_malloc(__n);
return __result;
} static void deallocate(void* __p, size_t /* __n */)
{
free(__p);
} static void* reallocate(void* __p, size_t /* old_sz */, size_t __new_sz)
{
void* __result = realloc(__p, __new_sz);
if ( == __result) __result = _S_oom_realloc(__p, __new_sz);
return __result;
}

当调用malloc和realloc申请不到内存空间的时候,会改调用oom_malloc()和oom_realloc(),这两个函数会反复调用用户传递过来的out of memory handler处理函数,直到能用malloc或者realloc申请到内存为止。如果用户没有传递__malloc_alloc_oom_handler,__malloc_alloc_template会抛出__THROW_BAD_ALLOC异常。所以,内存不足的处理任务就交给类客户去完成。

2 __default_alloc_template分配器

这个分配器采用了内存池的思想,有效地避免了内碎片的问题(顺便一句话介绍一下内碎片和外碎片:内碎片是已被分配出去但是用不到的内存空间,外碎片是由于大小太小而无法分配出去的空闲块)。如果申请的内存块大于128bytes,就将申请的操作移交__malloc_alloc_template分配器去处理;如果申请的区块大小小于128bytes时,就从本分配器维护的内存池中分配内存。分配器用空闲链表的方式维护内存池中的空闲空间,空闲链表大概类似于下面的形状:

如上图所示,s_free_list是这些空闲分区链的起始地址组成的数组,大小为16。这16个链表中每个链表中的空闲空间的大小都是固定的,第一个链表的空闲块大小是8bytes, 依次是16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128bytes。

  另外还有三个指针s_start_free, s_end_free, s_heap_size。它们分别指向整个内存池的起始地址,结束地址和可用空间大小。

分配内存过程:

  1)如果申请的内存空间大于128bytes, 则交由第一个分配器处理

  2)分配器首先将申请内存的大小上调至8的倍数n,并根据n找出其对应的空闲链表地址__my_free_list

  3)如果该空闲链表中有可用的空闲块,则将此空闲块返回并更新__my_free_list,否则转到4)

  4)到这一步,说明__my_free_list中没有空闲块可用了,分配器会按照下面的步骤处理:

  a) 试着调用_s_chunk_alloc()申请大小为n*20的内存空间,注意的是,此时不一定能申请到n*20大小的内存空间

b) 如果只申请到大小为n的内存空间,则返回给用户,否则到c)

c) 将申请到的n*x(a中说了,不一定是n*20)内存块取出一个返回给用户,其余的内存块链到空闲链表__my_free_list中

_s_chunk_alloc()的具体过程为:

1)如果_s_start_free和_s_end_free之间的空间足够分配n*20大小的内存空间,则从这个空间中取出n*20大小的内存空间,更新_s_start_free并返回申请到的内存空间的起始地址,否则转到2)

2) 如果_s_start_free和_s_end_free之间的空间足够分配大于n的内存空间,则分配整数倍于n的内存空间,更新_s_start_free,由nobj返回这个整数,并返回申请到的内存空间的起始地址;否则转到3)

  3) 到这一步,说明内存池中连一块大小为n的内存都没有了,此时如果内存池中还有一些内存(这些内存大小肯定小于n),则将这些内存插入到其对应大小的空闲分区链中

  4) 调用malloc向运行时库申请大小为(2*n*20 + 附加量)的内存空间, 如果申请成功,更新_s_start_free, _s_end_free和_s_heap_size,并重新调用_s_chunk_alloc(),否则转到5)

  5) 到这一步,说明4)中调用malloc失败,这时分配器依次遍历16个空闲分区链,只要有一个空闲链,就释放该链中的一个节点,重新调用_s_chunk_alloc()

内存释放过程:

内存的释放过程比较简单,它接受两个参数,一个是指向要释放的内存块的指针p,另外一个表示要释放的内存块的大小n。分配器首先判断n,如果n>128bytes,则交由第一个分配器去处理;否则将该内存块加到相应的空闲链表中。

SGI STL 为了方便用户访问,为上面提到的两种分配器包装了一个接口(对外提供的分配器接口),该接口如下:

 template<class _Tp, class _Alloc>
class simple_alloc { public:
static _Tp* allocate(size_t __n)
{ return == __n ? : (_Tp*) _Alloc::allocate(__n * sizeof (_Tp)); }
static _Tp* allocate(void)
{ return (_Tp*) _Alloc::allocate(sizeof (_Tp)); }
static void deallocate(_Tp* __p, size_t __n)
{ if ( != __n) _Alloc::deallocate(__p, __n * sizeof (_Tp)); }
static void deallocate(_Tp* __p)
{ _Alloc::deallocate(__p, sizeof (_Tp)); }
};

用户调用分配器的时候,为simple_alloc的第二个模板参数传递要使用的分配器。

vector(用户方式)使用STL分配器的代码,可以看到vector的基类调用simple_alloc作为其分配器:

 template <class _Tp, class _Alloc>
  //cobbliu 注:STL vector 的基类
class _Vector_base {  
public:
typedef _Alloc allocator_type;
allocator_type get_allocator() const { return allocator_type(); } _Vector_base(const _Alloc&)
: _M_start(), _M_finish(), _M_end_of_storage() {}
_Vector_base(size_t __n, const _Alloc&)
: _M_start(), _M_finish(), _M_end_of_storage()
{
_M_start = _M_allocate(__n);
_M_finish = _M_start;
_M_end_of_storage = _M_start + __n;
} ~_Vector_base() { _M_deallocate(_M_start, _M_end_of_storage - _M_start); } protected:
_Tp* _M_start;
_Tp* _M_finish;
_Tp* _M_end_of_storage; typedef simple_alloc<_Tp, _Alloc> _M_data_allocator;
_Tp* _M_allocate(size_t __n)
{ return _M_data_allocator::allocate(__n); }
void _M_deallocate(_Tp* __p, size_t __n)
{ _M_data_allocator::deallocate(__p, __n); }
};

基本内存处理工具

除了上面的内存分配器之外,STL还提供了三类内存处理工具:uninitialized_copy(), uninitialized_fill()和uninitialized_fill_n()。这三类函数的实现代码在头文件stl_uninitialized.h中。

uninitialized_copy()像下面的样子:

 template <class _InputIter, class _ForwardIter>
inline _ForwardIter
uninitialized_copy(_InputIter __first, _InputIter __last,
_ForwardIter __result)
{
return __uninitialized_copy(__first, __last, __result,
__VALUE_TYPE(__result));
}

uninitialized_copy()会将迭代器_first和_last之间的对象拷贝到迭代器_result开始的地方。它调用的__uninitialized_copy(__first, __last, __result,__VALUE_TYPE(__result))会判断迭代器_result所指的对象是否是POD类型(POD类型是指拥有constructor, deconstructor, copy, assignment函数的类),如果是POD类型,则调用算法库的copy实现;否则遍历迭代器_first~_last之间的元素,在_result起始地址处一一构造新的元素。

uninitialized_fill()像下面的样子:

 template <class _ForwardIter, class _Tp>
inline void uninitialized_fill(_ForwardIter __first,
_ForwardIter __last,
const _Tp& __x)
{
__uninitialized_fill(__first, __last, __x, __VALUE_TYPE(__first));
}

uninitialized_fill()会将迭代器_first和_last范围内的所有元素初始化为x。它调用的__uninitialized_fill(__first, __last, __x, __VALUE_TYPE(__first))会判断迭代器_first所指的对象是否是POD类型的,如果是POD类型,则调用算法库的fill实现;否则一一构造。

uninitialized_fill_n()像下面这个样子:

 template <class _ForwardIter, class _Size, class _Tp>
inline _ForwardIter
uninitialized_fill_n(_ForwardIter __first, _Size __n, const _Tp& __x)
{
return __uninitialized_fill_n(__first, __n, __x, __VALUE_TYPE(__first));
}

uninitialized_fill_n()会将迭代器_first开始处的n个元素初始化为x。它调用的__uninitialized_fill_n(__first, __n, __x, __VALUE_TYPE(__first))会判断迭代器_first所指对象是否是POD类型,如果是,则调用算法库的fill_n实现;否则一一构造。

STL六大组件之——分配器(内存分配,好深奥的东西)的更多相关文章

  1. STL —— STL六大组件

    注:以下内容摘自 http://blog.csdn.net/byxdaz/article/details/4633826 STL六大组件 容器(Container) 算法(Algorithm) 迭代器 ...

  2. (转)C++ STL中的vector的内存分配与释放

    C++ STL中的vector的内存分配与释放http://www.cnblogs.com/biyeymyhjob/archive/2012/09/12/2674004.html 1.vector的内 ...

  3. STL六大组件简介

    一.STL简介 (一).泛型程序设计 泛型编程(generic programming) 将程序写得尽可能通用 将算法从数据结构中抽象出来,成为通用的 C++的模板为泛型程序设计奠定了关键的基础 (二 ...

  4. STL 六大组件 功能与运用

    STL 提供六大组件,彼此可以组合套用: 1 容器(containers):各种数据结构,如vector,list,deque,set,map,用来存放数据,从实现的角度来看,STL容器是一种clas ...

  5. [转贴]从零开始学C++之STL(一):STL六大组件简介

    一.STL简介 (一).泛型程序设计 泛型编程(generic programming) 将程序写得尽可能通用 将算法从数据结构中抽象出来,成为通用的 C++的模板为泛型程序设计奠定了关键的基础 (二 ...

  6. STL六大组件之——容器知识大扫盲

    STL中的容器主要涉及顺序容器类型:vector.list.deque,顺序容器适配器类型:stack.queue.priority_queue.标准库中的容器分为顺序容器和关联容器.顺序容器(seq ...

  7. STL六大组件之——适配器代表大会

    适配器也是一种常用的设计模式: 将一个类的接口转换为另一个类的接口,使得原本因接口不兼容而不能合作的两个类可以一起运作.STL提供三种适配器:改变容器接口的容器适配器.改变迭代器接口的迭代器适配器以及 ...

  8. STL六大组件之——迭代器这个东西

    迭代器:除了在其它语言中司空见惯的下标法访问容器元素之外,C++语言提供了一种全新的方法——迭代器(iterator)来访问容器的元素.迭代器其实类似于引用,指向容器中某一元素.换个方式来说,容器就是 ...

  9. STL六大组件之——算法小小小小的解析

    参考自侯捷的<stl源码剖析> stl算法主要分为非可变序列算法(指不直接修改其所操作的容器内容的算法),可变序列算法(指可以修改它们所操作的容器内容的算法),排序算法(包括对序列进行排序 ...

随机推荐

  1. XML文件操作学习(一)

    受人启发,从今天开始也把学到的东西记在博客里加深印象,并且完成这个梳理过程. 最近大多数的时间都花费在做系统配置上了.大部分的配置比较复杂的都用xml文件来存储.暂时发现有以下几点需要注意的地方. 今 ...

  2. __init__ 和 self

    看代码 class A: def __init__(self, val): self.name = val def printName(self): print self.name a = A(&qu ...

  3. html 用图片代替重置按钮

    提交时,若把按钮设置成图片提交特别方便只要 <input type="image" alt="点此提交" src="images/button. ...

  4. python -- 一致性Hash

    python有一个python模块--hash_ring,即python中的一致性hash,使用起来也挺简单. 可以参考下官方例子:https://pypi.python.org/pypi/hash_ ...

  5. 在LINUX上创建GIT服务器【转】

    转自:http://blog.csdn.net/xiongmc/article/details/9176785 如果使用git的人数较少,可以使用下面的步骤快速部署一个git服务器环境. 1. Cli ...

  6. python之for学习

    for i in range(100,-1,-1): print "%s\n"%i; import os   path = 'D:\\Test'        for root, ...

  7. Android Touch(4)我不知道的MotionEvent(*)

    1,MotionEvent的复制或构造 有时候可能要保存一个MotionEvent, 它的构造方法是匿名的,不能直接创建,对外提供的获取对象的接口是静态的obtain方法,可以从一个MotionEve ...

  8. wdcp v3 Forbidden :You don't have permission to access /phpmyadmin on this server

    First edit the file /www/wdlinux/apache/conf/vhost/00000.default.conf and add the additional line to ...

  9. phpcms二级菜单

    二级 {pc:content action="category" catid="0" siteid="$siteid" order=&quo ...

  10. ORA-10456:cannot open standby database;media recovery session may be in process

    http://neeraj-dba.blogspot.com/2011/10/ora-10456-cannot-open-standby-database.html   Once while star ...