NoFear

导航

SGI_STL_空间配置器

Posted on 2012-09-10 12:59  Fear_Hao  阅读(1748)  评论(0编辑  收藏  举报

一般而言,我们习惯C++内存申请操作和释放操作如下:

class Foo{};

Foo * pf = new Foo;  //1.申请空间2.调用构造函数

delete pf;                //1.析构函数2.释放空间

 

但是为了精确分工,STL Allocator这两个阶段分开来。PS:以下代码不讨论多线程情况

内存申请:alloc::allocate()负责。

内存释放:alloc::deallocate()负责。

对象构造:全局 ::construct()负责。

对象析构:全局 ::destroy()负责。

 

 

1.<stl_construct.h> 对象构造和析构关键代码

【::construct()和::destroy()被设计为全局函数。::destory()有2个版本,第一个版本接受一个指针,调用该析构函数即可,第二个版本考虑到大范围的对象析构可能无关痛痒(trivial destructor),使用value_type()获得迭代器所指对象的型别,再利用__tyep_traits<T>判断trivial?不做:调用第一个版本】

#include <new.h>        //为了使用placement new

template <class T1, class T2>
inline void construct(T1* p, const T2& value) 
{
    new (p) T1(value);     // placement new; 显示调用构造函数T1(value);
}

// 以下是destroy() 第一版本,接受一个对象指针。
template <class T>
inline void destroy(T* pointer) 
{
    pointer->~T();    // 析构
}

// 以下是destroy() 第二版本,接受两个迭代器,此函数找出元素得数值类型
// 利用__type_traits<> 采用适当措施
template <class ForwardIterator>
inline void destroy(ForwardIterator first, ForwardIterator last) 
{
    __destroy(first, last, value_type(first));
}

// 判断对象的value type是否有trivial destructor
template <class ForwardIterator, class T>
inline void __destroy(ForwardIterator first, ForwardIterator last, T*) 
{
    typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;
    __destroy_aux(first, last, trivial_destructor());
}

// 如果对象的型別(value type)有non-trivial(要紧的) destructor
//循环调用destroy一个版本
template <class ForwardIterator>
inline void __destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) 
{
    for ( ; first < last; ++first)
        destroy(&*first);
}

// 如果对象的型別(value type)有trivial(无关紧要的) destructor
template <class ForwardIterator> 
inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) 
{
    //不处理
}

// 以下是destroy() 第二版本針對迭代器為char* 和wchar_t* 的特化版
inline void destroy(char*, char*) {}
inline void destroy(wchar_t*, wchar_t*) {}

 

2.<stl_alloc.h>     空间的申请和释放

【C++内存申请基本操作:::operator new(),内存释放基本操作:::operator delete()。SGI用malloc和free这2个C函数代替完成内存的申请和释放。考虑到内存碎片问题,SGI设计了两级配置器,第一级直接使用malloc和free,包括了内存不足处理机制,需要调用者自己提供。第二级判断配置区超过128字节,调用第一级配置器。小于128字节,采用内存池管理】

 

2.1 配置器接口

//简单的转调用,可能第一级或者第二级配置器

template<class T, class Alloc>
class simple_alloc 
{
public:
    static T *allocate(size_t n)
    { 
        return 0 == n? 0 : (T*) Alloc::allocate(n * sizeof (T)); 
    }
    static T *allocate(void)
    { 
        return (T*) Alloc::allocate(sizeof (T)); 
    }
    static void deallocate(T *p, size_t n)
    { 
        if (0 != n) Alloc::deallocate(p, n * sizeof (T)); 
    }
    static void deallocate(T *p)
    { 
        Alloc::deallocate(p, sizeof (T)); 
    }
};

 

2.2 vector简单使用例子

template<class T, class Alloc>
class vector
{
protected:
    typedef simple_alloc<value_type,Alloc> data_allocator;

    void deallocate()
    {
        if(...)
            data_allocator::deallocate(start,end_of_storage - start);
    }
};

 

2.3 第一级配置器

【SGI第一级配置器的allocate和realloc都是在调用C函数malloc和realloc不成功后,改调用oom_malloc和oom_realloc,后两者都有内循环,不断调用你指定的内存不足处理函数】

// Malloc-based allocator.  Typically slower than default alloc below.
// Typically thread-safe and more storage efficient.
#ifdef __STL_STATIC_TEMPLATE_MEMBER_BUG
# ifdef __DECLARE_GLOBALS_HERE
void (* __malloc_alloc_oom_handler)() = 0;
// g++ 2.7.2 does not handle static template data members.
# else
extern void (* __malloc_alloc_oom_handler)();
# endif
#endif

//inst没排上用场
template <int inst>
class __malloc_alloc_template 
{
private:

    //处理内存不足
    static void *oom_malloc(size_t);
    static void *oom_realloc(void *, size_t);
#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
    static void (* __malloc_alloc_oom_handler)();
#endif

public:
    
    static void * allocate(size_t n)
    {
        //第一级直接用malloc;
        void *result = malloc(n);    
        //malloc内存不足补救函数
        if (0 == result)
            result = oom_malloc(n);
        return result;
    }

    static void deallocate(void *p, size_t /* n */)
    {    
        //直接free
        free(p);
    }

    static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz)
    {
        //直接使用realloc 下面为补救函数
        void * result = realloc(p, new_sz);
        if (0 == result) 
            result = oom_realloc(p, new_sz);
        return result;
    }

    static void (* set_malloc_handler(void (*f)()))()
    {
        //指定你的out-of-memory处理函数
        void (* old)() = __malloc_alloc_oom_handler;
        __malloc_alloc_oom_handler = f;
        return(old);
    }
};

// malloc_alloc out-of-memory handling

#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
template <int inst>
//你的out-of-memory处理函数初始为
void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0;
#endif

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)();    
        //再一次申请
        result = malloc(n);
        if (result) 
            return(result);
    }
}

template <int inst>
void * __malloc_alloc_template<inst>::oom_realloc(void *p, 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)();
        result = realloc(p, n);
        if (result)
            return(result);
    }
}

 

2.4 第二级配置器

【每次先申请一大块内存作为内存池,并且维护16条链表,分别从8-16-24-32……-128字节(配置器会主动将当前内存申请提升到8的倍数),链表负责配置出去(配置出去时链表不再指向)和回收回来(回收回来时链表重新指向),当链表空间不足时向内存池申请。】

 

2.4.1 链表节点结构

//利用union的性质使得链表既是指针又是实际区块
union obj
{
    union obj * free_list_link;
    char client_data[1];       //柔性数组
};

 

2.4.2 配置器类内代码

enum {__ALIGN = 8};
enum {__MAX_BYTES = 128};
enum {__NFREELISTS = __MAX_BYTES/__ALIGN};
#endif

template <bool threads, int inst>
class __default_alloc_template {

private:
    //扩展到__ALIGN的倍数也即是的倍数
    static size_t ROUND_UP(size_t bytes) 
    {
        return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));
    }
private:
    union obj
    {
        union obj * free_list_link;
        char client_data[1];    /* The client sees this.        */
    };
private:
    //16条链表
    static obj *  free_list[__NFREELISTS]; 
    
    //根据申请的大小快速找到所属链表
    static  size_t FREELIST_INDEX(size_t bytes) 
    {
        return (((bytes) + __ALIGN-1)/__ALIGN - 1);
    }
    
    //向内存池申请n大小的区间n为的倍数
    static void *refill(size_t n);

    //内存池成处理申请函数一般为一次性配置nobjs个size大小的块nobjs默认为
    //不足时,nobjs返回不定数目
    static char *chunk_alloc(size_t size, int &nobjs);

    //内存池起始位置只在chunk_alloc变化
    static char *start_free;
    //内存池结束位置只在chunk_alloc变化
    static char *end_free;
    //内存池heap_size
    static size_t heap_size;

public:
    //申请
    static void * allocate(size_t n);
    //回收
    static void deallocate(void *p, size_t n);
    static void * reallocate(void *p, size_t old_sz, size_t new_sz);

} ;

 

2.4.3 allocate()实现

/* n must be > 0      */
static void * allocate(size_t n)
{
    obj * __VOLATILE * my_free_list;
    obj * __RESTRICT result;

    //大于字节就调用第一级配置器
    if (n > (size_t) __MAX_BYTES) 
    {
        return(malloc_alloc::allocate(n));
    }

    //找到链表中的所属链表
    my_free_list = free_list + FREELIST_INDEX(n);
    
    result = *my_free_list;
    //链表没有可用的块调用refill申请
    if (result == 0)
    {
        void *r = refill(ROUND_UP(n));
        return r;
    }
    //调整链表不再指向这块被使用的块
    *my_free_list = result -> free_list_link;
    return (result);
};

 

2.4.4 deallocate()实现

/* p may not be 0 */
static void deallocate(void *p, size_t n)
{
    obj *q = (obj *)p;
    obj * __VOLATILE * my_free_list;
    
    //大于使用第一级配置器
    if (n > (size_t) __MAX_BYTES) 
    {
        malloc_alloc::deallocate(p, n);
        return;
    }
    //找到所属链表
    my_free_list = free_list + FREELIST_INDEX(n);
    
    //调整链表回收区块
    q -> free_list_link = *my_free_list;
    //链表重新指向回收块
    *my_free_list = q;

}

 

2.4.5 refill()实现

void* __default_alloc_template<threads, inst>::refill(size_t n)
{
    //默认申请块数
    int nobjs = 20;
    //nobjs为传引用
    char * chunk = chunk_alloc(n, nobjs);
    obj * __VOLATILE * my_free_list;
    obj * result;
    obj * current_obj, * next_obj;
    int i;
    
    //只有一块返回给调用者链表无新节点
    if (1 == nobjs) 
        return(chunk);

    //不止一块找到所属链表
    my_free_list = free_list + FREELIST_INDEX(n);

    //对新的区块建链表
    result = (obj *)chunk;
    //链表指向从内存池拿出的链表
    *my_free_list = next_obj = (obj *)(chunk + n);
    //将新链表之间的节点串接起来
    for (i = 1; ; i++)  //从开始第个返回给申请者
    {
        //当前节点
        current_obj = next_obj;
        //指向下一个节点
        next_obj = (obj *)((char *)next_obj + n);
        if (nobjs - 1 == i) 
        {
            //尾节点置
            current_obj -> free_list_link = 0;
            break;
        } 
        else 
        {
            //当前节点指向下一个节点
            current_obj -> free_list_link = next_obj;
        }
    }
    return(result);   //返回给申请者
}

 

2.4.6 内存池chunk_alloc()实现

_default_alloc_template<threads, inst>::chunk_alloc(size_t size, int& nobjs)
{
    char * result;
    //申请总量
    size_t total_bytes = size * nobjs;
    //内存池余额
    size_t bytes_left = end_free - start_free;
    
    //余额满足申请总量
    if (bytes_left >= total_bytes) 
    {
        //返回result
        result = start_free;
        //内存池可用起始地址+=申请总量
        start_free += total_bytes;
        return(result);
    }
    //不足以满足申请总量但是可以供应一个以上区块
    else if (bytes_left >= size) 
    {
        //重新得到能够供应个数
        nobjs = bytes_left/size;
        //重新计算总量
        total_bytes = size * nobjs;
        //同上
        result = start_free;
        start_free += total_bytes;
        return(result);
    }
    //一个区块都提供不了
    else 
    {

        size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);

        //余额大于把这些零头凑到链表里去
        if (bytes_left > 0) 
        {
            obj * __VOLATILE * my_free_list = free_list + FREELIST_INDEX(bytes_left);
    
            ((obj *)start_free) -> free_list_link = *my_free_list;
            *my_free_list = (obj *)start_free;
        }
        //申请heap空间,补充内存池
        start_free = (char *)malloc(bytes_to_get);
        if (0 == start_free) 
        {
            int i;
            obj * __VOLATILE * my_free_list, *p;
            // Try to make do with what we have.  That can't
            // hurt.  We do not try smaller requests, since that tends
            // to result in disaster on multi-process machines.

            //以下搜索适当的链表
            //适当是指"尚有未用区块的链表"
            for (i = size; i <= __MAX_BYTES; i += __ALIGN) 
            {
                //拿到这个链表
                my_free_list = free_list + FREELIST_INDEX(i);
                p = *my_free_list;
                //
                if (0 != p)
                {
                    //调整链表释放未使用区块
                    //链表起始指向下一个区块
                    *my_free_list = p -> free_list_link;
                    //内存池开头指向这个区块
                    start_free = (char *)p;
                    //结尾指向区块结尾
                    end_free = start_free + i;
                    //再一次调用自身
                    return(chunk_alloc(size, nobjs));
                    // Any leftover piece will eventually make it to the
                    // right free list.
                }
            }
            //没办法没内存用了
            end_free = 0;    // In case of exception.
            //调用第一级配置器看看out-of-memory机制能否出点力
            start_free = (char *)malloc_alloc::allocate(bytes_to_get);
            // This should either throw an exception
            // or remedy the situation.  Thus we assume it
            // succeeded.
        }
        //heap_size+=
        heap_size += bytes_to_get;
        //结束位置重新界定
        end_free = start_free + bytes_to_get;
        return(chunk_alloc(size, nobjs));
    }
}