STL:allocator 空间配置器

Posted 小键233

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STL:allocator 空间配置器相关的知识,希望对你有一定的参考价值。

大概…………很久很久以前,我做了一个浪(er)漫(bi)的决定,就是自己也实现一个STL,毕竟造轮子呀,才是最浪漫的不是吗。

于是,山很高,海很深,浪花一浪接一浪,我义无反顾走上了作死之路。
nice ,作死之路的第一部分,就是关于allocator,空间配置器的。

STL 有自己的内存管理,其中最最基础的就是allocator ,任何内存的请求和释放都要经过它。

它本质上就是封装了一下malloc 和free 函数。
对的,它并不是使用 operator new 和operator delete 来分配和回收内存,而使用了c 函数的malloc 和free 。事实上,我也很喜欢malloc 和free 函数:)

在接下去阅读之前,我已经假设,你已经懂得traits 技术(萃取),placement new 、new_handler的应用。如果你对这个一头雾水,那么建议你先了解一下或者看看我以往的博文。traits 技术new

准备工作

首先,你可以去下载SGI 版本的STL 源码,我就是依据这个版本的学习的。在它的官网可以下载。
我建议同时下载最新的版本和STL30 的版本。因为最新的版本支持C++11 的标准,但是直接阅读的话并不是那么友好。而STL30 的版本呢直接阅读就清晰明了了。

其次,你在实现的过程中,还可以打开C++ 的官网,查询一下标准是怎么样的,然后就可以根据标准来实现自己的版本。

为了避免命名冲突,可以把代码写入自己自定义的一个命名空间中。

一级配置器

在STL 中,allocator 是分为一级和二级的。一级的就处理超过128bytes 的数据,二级的就处理小于128bytes 的数据。但是二级的空间配置器太复杂了,这里不再详述,但是强烈建议了解一下。

C++ 的标准是,空间配置器要完成内存的分配和构造功能。
但是SGI 中的空间配置器灰常傲娇。
它的allocator 是使用C函数 malloc ,但是C++ 中的new 是要有内存分配和对象构造两部分组成的。单单malloc 是完成不了一个 new 的工作量的,那怎么办呢?
使用placement new。事实上,STL 中的内存分配和对象构造是分开执行的,不同的函数负责执行不同的功能。我觉得很明智,毕竟我不是那么喜欢new。

SGI 的傲娇,还体现在命名上,C++ 的空间配置器的标准名称是allocator ,但是它的叫alloc。
它也做了一些兼容的工作,所以,里面也有符合C++ 标准的版本

这部分的内容在文件stl_alloc.h 中。

alloc

代码如下:


typedef void (*alloc_handler)();

//我也不知道为什么要设为模板,标准库就是这么实现的
//难道是为了以后的扩展?
template<int inst>
class __alloc_template

    //处理 out of memeory 的
    static void* _xj_oom_malloc(size_t);
    static void* _xj_oom_realloc(void*, size_t);

    static alloc_handler __alloc_oom_handler; //用来存放处理的指针
public:
    static void* allocate(size_t __n)
    
        void* result = malloc(__n);
        if(result == 0) result = _xj_oom_malloc(__n);
        return result;
    
    static void deallocate(void* p, size_t /* __n */ )
    
        free(p);
    
    //没有做证同测试,比较信任realloc 吧
    static void* reallocate(void* p, size_t  __n  ,size_t __new_size )
    
        void* result = realloc(p, __new_size);
        if(0 == result) result = _xj_oom_realloc(p, __new_size);
        return result;
    
    static alloc_handler set_alloc_handler ( alloc_handler __new_handler)
    
        alloc_handler temp(__alloc_oom_handler);
        __alloc_oom_handler = __new_handler;
        return temp;
    
;

template<int inst> 
alloc_handler __alloc_template<inst>::__alloc_oom_handler  =0;

template<int inst>
void* __alloc_template<inst>::_xj_oom_malloc(size_t _s)

    alloc_handler my_handler;
    void* result;
    while(true)
    
        my_handler = __alloc_oom_handler;
        if(0 == my_handler)  __XJ_THROW_BAD_ALLOC; 
        my_handler();
        result = malloc(_s);
        if(result) return result;
    


template<int inst>
void* __alloc_template<inst>::_xj_oom_realloc(void*p, size_t _s)

    alloc_handler my_handler;
    void* result;
    while(true)
    
        my_handler = __alloc_oom_handler;
        if(0 == my_handler)  __XJ_THROW_BAD_ALLOC; 
        my_handler();
        result = realloc(p, _s);
        if(result) return result;
    


typedef __alloc_template<0> alloc;

它的做法是,实现一个模板类先,然后再typedef 一下,就变成广泛使用的alloc 名称了。正如注释所言,我不知道理由在哪里,唯一能想到的就是为了以后的扩展了。

分配内存使用allocate 函数
回收内存使用deallocate 函数。虽然名义上接受两个参数,但是实际上只需要一个就可以了,因为free 只要一个函数。

它还模拟了分配内存,空间不足时的new_handler 行为。
__alloc_oom_handler 就是用来存储处理的句柄的,但是除非你指定,不然它是空指针的。
reallocate 就是在已有的空间基础上重新分配内存。

allocate 函数行为很明确,如果请求不到内存,那么就丢给_xj_oom_malloc 来处理。而_xj_oom_malloc 中有一个无限循环,不停地调用__alloc_oom_handler 函数(当然这个函数指针必须不为0),试图做一点最后的处理。
如果__alloc_oom_handler 也无能为力,那么只要throw 异常。

其中那个宏实际如下:

#define __XJ_THROW_BAD_ALLOC throw std::bad_alloc()

这就是alloc 的全部代码了。

simple_alloc

但是,alloc 并没有和类型相关,每次分配内存的时候,都要额外计算实际的内存值。
比如说,我要分配一个int ,我希望使用的类似于allocate(1) 这样的,而不是allocate(1*sizeof(int)
于是,我们要需要一个类封装一下alloc,如下:

/**
* 这是STL使用的,但这个不可以直接去套用
*/
template<typename T, typename Alloc>
class simple_alloc

public:
    static T* allocate(size_t n)
     return n!=0 ? (T*) Alloc::allocate(n*sizeof(T) ) :0 ; 
    static T* allocate()
    
        return (T*) Alloc::allocate(sizeof(T) );
    
    static void deallocate(T* p, size_t n)
    
        Alloc::deallocate(p, n*sizeof(T) );
    
    static void deallocate(T* p)
    
        Alloc::deallocate(p, sizeof(T) );
    
;

一个值得注意的问题就是申请的内存为0时,怎么处理。

使用的时候实例化simple_alloc 就可以了。

标准的allocator

看一下标准的allocator 是怎么玩的:

/**
* 下面这个是C++ 规定的标准接口,但是SGI 的STL 并不使用这个接口
* 反而使用更简单一点的,这个可以直接套用
*/
template<typename T>
class allocator

    typedef alloc _Alloc;
public:
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;
    typedef T* pointer;
    typedef const T* const_pointer;
    typedef T& reference;
    typedef const T& const_reference;
    typedef T value_type;


    allocator() 
    allocator(const allocator& ) 
    template<typename T1>
    allocator(const allocator<T1>& ) 
    ~allocator ()    

    //暂时还不明白这个怎么用
    template<typename T1> struct rebind
    
        typedef allocator<T1> other;
    ;

    T* address(T& _x)  return &_x; 
    const T* address(const T& _x)  return &_x; 

    //__n 不能为0,C++ 没有说为什么
    //后面那个参数我也不知道是干嘛的   
    T* allocate(size_t  __n, const void* = 0)
    
        return __n != 0 ? static_cast<T*>( _Alloc::allocate(__n * sizeof(T) ) ) : 0;
    

    //p 不能为nullptr
    void deallocate(T* p, size_t s)
    
        _Alloc::deallocate(p, s*sizeof(T) );
    

    //size_t (-1) 应该是利用了补码的表现形式
    size_t max_size() const 
       return size_t(-1) / sizeof(T); 

    //construct he  destroy
    void construct(T* p, const T& value)
    
        new (p) T(value);
    

    void destroy(T* p)  p->~T(); 
;

template<>
class allocator<void>

public:
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;
    typedef void* pointer;
    typedef const void* const_pointer;
    typedef void value_type;

    template<typename T> struct rebind
    
        typedef allocator<T> other;
    ;
;

这个allocator 在G++ 中,可以直接使用:

vector<int, allocator> v;

代码在github 上可以得到。如果中间有变更,不保证博客的代码永远最新。

二级配置器

二级的空间配置器是处理申请小于128bytes 的内存。它使用了内存池的概念。
由于比较复杂,这里不再详述。
但是,二级配置器的一个问题在于,小的内存碎片永远不会被真正free ,还给系统。本来就是为了效率而生,怎么使用倒是看代码的需求吧。

以上是关于STL:allocator 空间配置器的主要内容,如果未能解决你的问题,请参考以下文章

STL 之 空间配置器(allocator)

STL 之 空间配置器(allocator)

STL之空间配置器allocator

STL学习笔记--2空间配置器 allocator

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

STL源码剖析——空间配置器Allocator#2 一/二级空间配置器