STL源码剖析--空间配置器(allocator)

1.简介

一般容器都需要一定空间存放数据,allocator就是用来配置空间的,SGI的allocator配置的对象时内存。
一个allocator通常包含两个部分,一是内存配置和内存释放(allocate的deallocate),二是对象构造和析构(construct和destory)。

2.一个简单的空间配置器

#ifndef _JJALLOC_
#define _JJALLOC_
#include<new>//for placement new
#include<cstdlib>//for ptrdiff_t,size_t
#include<climits>//for UINT_MAX
#include<iostream>//for cerr
 
namespace JJ
{
 
 template<class T>
 inline T* _allocate(ptrdiff_t size,T*){   
	 //当operator new申请一个内存失败的时候.
	 //如果存在客户指定的处理函数,则调用处理函数(new_handler),如果不存在则抛出一个异常
	 //参数为0,则抛出异常
	 set_new_handler(0);
	 T* tmp=(T*)(::operator new((size_t)(size*sizeof(T))));
	 if(tmp==0){
		 cerr<<"out of memory"<<endl;
		 exit(1);
	 }
	 return tmp;
 }
 
 template<class T>
 inline void _deallocate(T* buffer){
	 ::operator delete(buffer);
 }
 
 template<class T1,class T2>
 inline void _construct(T1*p,const T2& value){
     new(p) T1(value);
 }
 template<class T>
 inline void _destroy(T* ptr){
     ptr->~T();
 }
 
 template<class T>
 class allocator{
 public:
	 typedef T           value_type;
	 typedef T*          pointer;
	 typedef const T*    const_pointer;
	 typedef T&          reference;
     typedef const T&    const_reference;
	 typedef size_t      size_type;
	 typedef ptrdiff_t   difference_type;
 
	 //rebind allocator of type U
	 template<class U>
	 struct rebind{
       typedef allocator<U> other;
	 };
 
	 pointer allocate(size_type n,const void* hint=0){
		 return _allocate((difference_type)n,(pointer)0);
	 }
 
	 void deallocate(pointer p,size_type n) {_deallocate(p);}
     
	 void construct(pointer p,const T& value){
		 _construct(p,value);
	 }
     
	 void destroy(pointer p){ _destroy(p);}
 
	 pointer address(reference x) { return (pointer)&x;}
 
	 const_pointer const_address(const_reference x) { 
		 return (const_pointer)&x;
	 }
	 size_type max_size() const{
	     return size_type(UINT_MAX/sizeof(T););
	 };
 };
 
} //end of namespace JJ
#endif _JJALLOC_

重点是JJ空间中四个模板函数:

  1. __allocate函数模板采用了operator new来配置内存,即分配一块未构造的、原始的内存空间。
  2. __deallocate利用operator delete释放上面分配的内存。
  3. __construct采用定位new在给定的内存地址上构造对象。
  4. __destory执行对象的析构动作。

四个部分构成了空间配置器的基础。

3.SGI空间配置器

SGI提供了两个空间配置器,其中标准的allocator与上面提供的简单空间配置器类似,效率并不高,也不推荐使用。
下面介绍SGI特殊空间配置器

3.1 SGI特殊空间配置器

SGI特殊的空间配置器称为alloc,alloc也是由之前讲述的两部分构成:一是内存配置和内存释放(allocate的deallocate),二是对象构造和析构(construct和destory)。

3.1.1构造和析构

以下源码在 <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));
}

第二个版本通过 __VALUE_TYPE判断是否有对象是否有trivial destructor(无关痛痒的析构函数),然后调用不同的__destroy_aux,如果有执行它的析构函数(_Destroy(&*__first)),如果没有就什么也不做,因此提高了效率:

template <class _ForwardIterator>
void
__destroy_aux(_ForwardIterator __first, _ForwardIterator __last, __false_type)
{
  for ( ; __first != __last; ++__first)
    _Destroy(&*__first);
}

template <class _ForwardIterator> 
inline void __destroy_aux(_ForwardIterator, _ForwardIterator, __true_type) {}

关于如何获得对象类型以及如何判断是否有trivial destructor,下一篇博文在介绍。

3.1.2空间的配置和释放

以下源码在 <stl_alloc.h>
SGI在空间配置上考虑了以下问题:

  • 向system heap申请空间
  • 考虑多线程的影响
  • 考虑内存不足的影响
  • 考虑过多小型区块可能造成的内存碎片问题

c++的内存配置基本操作是:operator new和operator delete,其相当于C语言的malloc和free函数。
考虑内存碎片的问题,SGI配置器采用双层级配置器:

  • 第一级采用malloc和free函数。
  • 第二级视情况采用不同策略,当申请内存大于128kB时,直接采用第一级配置器;当小于时,采用内存池整理方式。
  • 是否开放第二级取决于 __USE_MALLOC是否定义。

不论采用第一级还是第二级,SGI都为他们包装了一个接口以供使用(simple_alloc)。

第一级配置器

第一级配置器采用malloc、free、realloc等执行实际内存操作,而不是使用operator new,因此不能直接运用c++ new-handler机制(即如果operator new无法完成操作,在抛出bad_alloc之前会先调用客户指定的处理程序,此程序称为new-handler)。需要自己实现new-handler机制:

static void (* set_malloc_handler(void (*f)()))()
{
    void (* old)() = __malloc_alloc_oom_handler;
    __malloc_alloc_oom_handler = f;
    return(old);
}

template <int inst>
void * __malloc_alloc_template<inst>::oom_malloc(size_t n)
{
    void (* my_malloc_handler)();
    void *result;

    for (;;) {
        my_malloc_handler = __malloc_alloc_oom_handler;  
        if (0 == my_malloc_handler) { __THROW_BAD_ALLOC; }
        (*my_malloc_handler)();  //调用new-handler
        result = malloc(n);
        if (result) return(result);
    }
}

如果malloc不成功,会改用oom_malloc不断申请,如果客户端未设置new-handler,则抛出异常。

第二级配置器

第二级配置器维护16个free-lists,分别管理8、16、24、32…120、128大小的区块,客户端配置内存时过程如下:

  1. 先从free-list中申请满足大小要求并且最小的区块,申请成功就返回给客户端并且该free-list的指针指向下一个区块。
  2. 如果在该free-list中没有申请到,就调用refill从内存池中申请20区块大小填充该free-list。

内存池的操作如下:
3. 如果内存池水量充足,就调出20个区块返回。
4. 如果水量不足,但还够一个以上区块,就调出这些区块然后返回。
5. 如果水量连一个区块都不足以提供,就从系统heap申请内存。在申请前,先把内存池中的零头分配给free-list,比如内存池中还剩35字节,就分配给区块大小为32的free-list。
6. 从系统申请40+n的内存,然后将第一个区块交给客户端,将剩余19个区块交给free-list维护,剩下的20+n个留给内存池。
7. 如果整个系统heap都不够了,就查找是否还有足够大且没有使用的free-list,然后释放这些区块(大的放入内存池,小的零头被编入free-list,通过递归调用自己实现)。

参考:STL源码剖析

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值