STL空间配置器(二)

空间的配置与释放------>std::alloc

上面是对象的构造与析构行为,那么接下来我们看看内存的配置与释放。

1)空间配置发生在对象构造前,空间释放发生在对象析构后。

2)由<stl_alloc.h>负责

3SGI对此的设计哲学如下:

       a)向system heap要求空间

       b)考虑多线程状态

       c)考虑内存不足时的应变措施

       d)考虑过多的“小型区块”可能造成的内存碎片问题。

(为了将问题控制在一定的复杂度内,在这里先不说多线程的问题与处理)

(4)SGI正是以malloc()free()完成内存的配置与释放。

(5)考虑到小型区块所可能造成的内存破碎的问题,SGI设计了双层级配置器。

       a)第一级配置器直接使用了malloc()free()

       b)第二级配置器则视情况采用不同的策略。采用策略如下:

               (一) 当配区块超过128bytes时,视之为“过大”,便调用第一级配置器;

               (二)当配置区块小于128bytes时,视之为“过小”,为了降低额外负担,便采用复杂的memory pool整理方式,而不再求助第一级配置器。

      c)整个设计究竟只开放第一级配置器,或者是同时开放第二级配置器,取决于_USE_MALLOC是否被定义;(这个是可以被测出来的)

           (如果定义了_USE_MALLOC,那么只开放第一级配置器,如果没有定义, 那么开放第二级配置器)

代码如下:
#ifdef _USE_MALLOC  
//第一级配置器  
typedef _malloc_alloc_template<0> malloc_alloc;  
typedef malloc_alloc alloc;
#else  
//第二级配置器 
typedef _default_alloc_template<__NODE_ALLOCATOR_THREADS,0> alloc  
#endif   
                

注:其中_malloc_alloc_template就是第一级配置器,_default_alloc_template就是第二级配置器。(alloc并不接受任何template型别参数)

                

无论alloc被定义为第一级或者第二级配置器,SGI还为他在包装一个接口如下,使配置器能够符合STL规格:

(因为我们将_malloc_alloc_template或者_default_alloc_template设置为底层的空间配置器,所以我们还需要设计一个包装函数使其符合常规的空间配置器的使用方式,还可以对底层实现进行更好的了封装)

/*
simple_alloc为底层的内存分配类的外部包装,其成员全部调用_malloc_alloc_template 的成员
*/  
template<typename T,class Alloc=alloc>  
class simple_alloc  
{  
public:  
    static T * allocate(void)  
    {  
        return (T *)Alloc::allocate(sizeof(T));  
    }    
    //此allocate接受一个指定对象个数的参数n
    static T * allocate(size_t n)  
    {        
	return n == 0 ? nullptr : (T *)Alloc::allocate(sizeof(T)*n);  
    }  
    static void deallocate(T * p)  
    {  
        Alloc::deallocate(p, sizeof(T));  
    }  
    static void deallocate(T * p, size_t n)  
    {  
        if (n != 0)  
            Alloc::deallocate(p);  
    }  
};  


这个接口使配置器的配置单位从bytes转为个别元素的大小(sizeof(T))SGI STL容器全部使用这个simple_alloc接口。

如图2-2 a:



接下来这个图片就是第一级配置器和第二级配置器的包装接口和运用方式:

如图2-2 b:


 (一)下面一起来看看第一级配置器:--->_malloc_alloc_template  

     第一级配置器的实质是使用了malloc,realloc,free,并且处理了内存不足(请求内存失败)时的情况。

    (因为::operator new提供了set_new_handler操作,使用户可以自己定制内存不足时的处理策略。而SGI STL没有使用::operator new,所以并不能使用C++set_new_handler,所以单独的实现了这个功能)

 

问题:没有使用::operator new操作的原因:

---->答:SGImalloc而非::operator new来配置内存。一个原因是历史因素,而另一个原因是C++并未提供相应于realloc()的内存配置操作。因此SGI不能直接使用C++set_new_handler(),必须仿真一个类似的ser_malloc_handler()

 

补充:而我们所提到的C++中的set_new_handler到底是什么呢?

---->答:在C++中分配内存我们通常会采用new/delete,那么当new申请内存时,可能会遇到内存不够的情况,此时我们会抛出一个out of memory异常。(set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用)


问题:为什么要set_new_handle函数呢?

---->答:因为这是处理内存分配的固定套路。这个标准库函数定义如下,有一个函数指针类型的返回值返回指向之前的回调函数的指针,随后再次尝试分配内存的操作,周而往复,只要参数不变,那么就可以在内存分配失败的时候形成一个循环,不断指向程序员指定的的操作。

 

首先,namespace std中有如下定义:

  Typedef void  (*new_handler)();

        new_handler  set_new_handler(new_handler  new_p) throw();//C++98

        new_handler  set_new_handler (new_handler  new_p) noexcept;//C++11

 

set_new_handler函数的理解:

1.set_new_handler函数的作用是设置new_p指向的函数为new操作或new[]操作失败时调用的处理函数。

2.设置的处理函数可以尝试使更多空间变为可分配状态,这样新一次的new操作就可能成功。当且仅当该函数成功获得更多可用空间它才会返回;否则它将抛出bad_alloc异常(或者继承该异常的子类)或者终止程序(例如调用abortexit)。

3.如果设置的处理函数返回了(例如,该函数成功获得了更多的可用空间),它可能将被反复调用,直到内存分配成功,或者它不再返回,或者被其它函数所替代。

4.在尚未用set_new_handler设置处理函数,或者设置的处理函数为空时,将调用默认的处理函数,该函数在内存分配失败时抛出bad_alloc异常。

5.在利用operator new请求内存分配失败的时候,在抛出异常之前,会调用一个回调函数给程序员一个机会处理内存分配失败的情况。new_p代表的是一个函数指针,指向程序员自己定义的用户处理内存分配失败时候的函数。在这段代码中如果new_p设置为nullptr或者0表示程序员放弃这个处理机会,直接抛出bad_alloc异常。

 

话题回来,继续说第一级配置器:

首先观察第一级配置器:

#if 0
#include <new>
#define _THROW_BAD_ALLOC  throw bad_alloc
#elif  !defined(_THROW_BAD_ALLOC)
#include <iostream.h>
#define _THROW_BAD_ALLOC  cerr<<"out of memory"<<endl ;  exit(1)
#endif
 
注:malloc-based allocator通常比稍后介绍的default alloc速度慢
 
//以下是第一级配置器
//注释:无”template型别参数”,至于”非型别参数”inst,则完全没派上用场
 
template<int inst>  
class _malloc_alloc_template  
{  
    /*
      oom_alloc为静态函数成员,用于处理malloc时的内存不足问题
      oom_realloc为静态函数成员,用于处理realloc时的内存不足问题
      _malloc_alloc_handler为静态数据成员,是void(*)()类型的函数指针,用于用户自己制定内存分配策略
    */  
    static void * oom_malloc(size_t); //out_of_memmory  malloc  
    static void * oom_realloc(void *, size_t);  
    static void(*_malloc_alloc_oom_handler)();  
public:  
    static void * allocate(size_t n)  
    {  
     //请求内存(第一级配置器直接使用malloc())  
        void * result = malloc(n);
     //如果内存不足,调用oom_malloc  
        if (result == nullptr)
            result=oom_malloc(n);
        return result;  
    }  
    static void * reallocate(void * p, size_t n)  
    {  
     //第一级配置器直接使用realloc()
        void *result = realloc(n);  
        if (result == nullptr)  
            result = oom_realloc(p, n);  
        return result;  
    }  
    static void deallocate(void * p)  
    {  
     //第一级配置器直接使用free()
     //使用free函数释放p地址后所分配的内存块  
        free(p);  
    }  
  
/*
以下仿真C++的set_new_handler(),换句话说,你可以通过它制定你自己的out_of_memory handler
*/
 
/*
此静态成员函数接受一个void(*)()类型的函数指针作为参数,返回void(*)()类型的函数指针。
其作用为使用用户自己定制的内存调度方法替换_malloc_alloc_handler,由此实现类似C++的set_new_handler方法。
*/  
  
    static void(* set_malloc_handler(void(*f)()))  ()  
    {  
        void(*old)() = _malloc_alloc_oom_handler;  
      //用用户自己定制的内存调度方法替换_malloc_alloc_handler
        _malloc_alloc_oom_handler = f;
        return old;  
    }  
};  
  
template<int inst>  
void(*_malloc_alloc_template<inst>::_malloc_alloc_oom_handler)() = 0;  
  
template<int inst>  
void * _malloc_alloc_template<inst>::oom_malloc(size_t n)  
{  
    void(*my_oom_handler)();  
    void * result;  
    //无限循环,直至成功分配内存或用户没有定制内存分配策略  
    for (;;)  
    {  
        my_oom_handler = _malloc_alloc_oom_handler;  
        if (my_oom_handler == nullptr)//如果用户没有定制内存分配策略  
        {
 		_THROW_BAD_ALLOC;
 	}
     //使用用户定制的方法(调用处理例程,企图释放内存)
        (*my_oom_handler)();       
        result = malloc(n);  //再次尝试配置内存
        if (result)  
            return result;  
    }  
}  
 
//此函数的设计思路与oom_malloc如出一辙  
template<int inst>  
void * _malloc_alloc_template<inst>::oom_realloc(void * p, size_t n)  
{  
    void(*my_oom_handler)();  
    void * result;  
    for (;;)  
    {  
        my_oom_handler = _malloc_alloc_oom_handler;  
        if (my_oom_handler == nullptr)  
            exit(1);  
        (*my_oom_handler)();  
        result = realloc(p,n);  
        if (result)  
            return result;  
    }  
}

 

注意,以下直接将参数inst指定为0

typedef  _malloc_alloc_template<0>  malloc_alloc;

 

注意,SGI第一级配置器的allocate()realloc()都是在调用malloc()realloc()不成功后,改调用oom_malloc()oom_realloc()。后两者都有内循环,不断调用内存不足处理例程,期望在某次调用之后,获得足够内存而圆满完成任务。但如果内存不足处理例程并未被客户端设定,oom_malloc()oom_realloc()便不客气的调用_THROW_BAD_ALLOC,抛出异常或者exit(1)终止程序。

 

同时要注意的是,设计与设定内存不足处理例程都是客户端的责任。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值