stl--分析空间配置器及源码实现

最近真是懒癌犯了,好久没动过博客了。开学两个月了,每天基本都“住”在网吧了,没日没夜和舍友打游戏。可是毕竟大三狗,找实习的压力真是大。是时候搞出点事情了。

前两天把stl的 空间配置器的思路搞懂了,今天就把一二级配置器自己实现了一番, 简直爽,源码果然博大精深,只可惜我道行不够,实现了好久,才把个中原理搞清楚一些。

现在就开始聊聊空间配置器吧

首先来说说一级空间配置器:

一级是最简单的了,其实就是封装了malloc和free以及realloc三大函数,不过作者有什么比较心思细微的地方呢?

来看看源码剖析一下

typedef void (*FUNC)();
template <int inst>
class __malloc_alloc_template
{
private:
    //未分配成功处理函数
    static void *oom_malloc(size_t);
    static void *oom_realloc(void *,size_t);

    //个人定制函数,用于释放空间
    static FUNC __malloc_alloc_oom_handler;
public:
    static void *allocate(size_t n)
    {
        void *result = NULL;

        result = malloc(n);
        if(result == NULL){
            result = oom_malloc(n);
        }

        return result;
    }
    static void *reallocate(void *p,size_t n)
    {
        void *result = NULL;

        result = realloc(p,n);
        if(result == NULL){
            result = oom_realloc(p,n);
        }

        return result;
    }
    static void *deallocate(void *p, size_t n)
    {
        free(p);
    }
    //返回之前的__malloc_alloc_oom_handler函数,把新函数给__malloc
    static FUNC set_malloc_handler(FUNC f)
    {
        FUNC old;

        old = __malloc_alloc_oom_handler;
        __malloc_alloc_oom_handler = f;

        return old;
    }
};

我只拷贝了一部分,加黑的如果了解了那么一级配置器就搞定了,首先说说看这个oom_malloc,当malloc失败时,这个函数就被调用。它的作用就是去调用用户自定义的释放空间的一个函数__malloc_alloc_oom_handler,不断的去释放空间,直到可以申请空间为止,如果实在不成功就会抛出异常,结束程序

oom_realloc和oom_malloc大同小异,几乎是一样的。惟一的区别在于前者是realloc申请空间,后者是malloc申请空间

接下来有意思的来了,可能很多看过stl源码剖析的同学(其实是我开始不懂啦- -)会对 void (*set_malloc_handler(void (*f)()))()产生疑问,妈的这时什么!!

好,现在我来解答这个疑问,看到typedef void (*FUNC)() 了么, FUNC是一种类型,它定义的变量是指向函数的指针,如果我把上面的函数写成这样是不是就比较好看懂了呢 static FUNC set_malloc_handler(FUNC f)  说白了, 就是一个函数它的参数是个指针,它的返回值也是个指针,就这样!!!

如果细一点说,那么参数和反回值都是指向函数的指针。懂了么~~

现在聊聊第二步,二级配置器
二级配置器就是在申请128字节以下会用到的东东。
来一张图片,对着图片详细说说咱们的二级空间配置器

针对这张图,首先来解释一下 横行那些是代表这一个空闲的数组(总共有16个元素,我没有画全),每一个元素都是一个指针。
其中这个空闲数组的每一个元素指向了它们自己对应的空闲链表

每一条空闲链表的每一块是根据对应 (数组下标+1)*8 作为一块的大小。
而start_free和end_free是多划分出的空闲区,并不属于哪个链表中。这一点稍后去说。
其实这个数据结构让我看来有种 哈希表的味道,啧啧。真是有点美味啊!!
其实这个数组是没有什么好说的,比较让人觉得制作新奇且高效的是在挂链表的节点操作上。
下面来说说
union obj
{
    union obj *free_list_link;
    char client_data[1];

};

可能有些同学不知道为什么用这个联合体,我来详细解释一下

这个联合体是在节点内部的,这样做的目的是节省了额外空间的开辟。那么肯定有同学就不了解既然在节点内部,拿节点的值不是把它覆盖了么,那怎么可能去保存下一个节点的位置

确实联合体的内容会被覆盖,但是被覆盖前它就已经去让空闲链表的指针指向下一个节点了,所以这是它精妙之处

这个就是用作存放指向下一个节点的地址的联合体。怎么操作这个联合体呢?如何去把节点挂上去呢?
恩~~两步就解决了
为了解决方便我自定义一个链表的首节点好了 union *list 链表的节点大小设置为n
接下来需要有两个辅助的指针,一个是obj *cur一个是 obj *next;

//先让next和cur存储为链表头节点地址
next = cur = list;
//因为链表地址空间连续,所以让next+n
next = (obj *)((char *)next + n);
//然后让cur的下一个节点指向next
cur->free_list_link = next;

如何回收呢?
回收的方法是这样的
根据传入的指针p和用的空间n
obj *l;
l = (obj *)p;
l->next = list;
list = l;
给一个图应该比较好懂一些

list指向的空闲链表开始的地方,p指向的是要释放的空间,目的就是让list指向p指向的位置就是这样,还有多的一步就是要把节点挂到链上。

现在可以讲讲空闲区间的问题了

空闲区间有3种去留:
(1).就是空闲区间
(2).被分配出去
(3).如果空闲区间不够要分配的空间,那么就会被挂到对应的链表上

大概就是这样,大体上就是这些,我下面把代码贴上来,可以运行的。

//二级空间配置器
enum{__ALIAN = 8}; //边界
enum{__MAX_BYTES = 128}; //最大分配块
enum{__NFREELISTS = __MAX_BYTES / __ALIAN};//空闲链表个数

template <bool threads, int inst>
class __default_alloc_template
{
public:
    static void *allocate(size_t n)
    {
        //一级的指针不能被编译器优化,因为申请空间时会迭代去挂链
        //很有可能编译器把它优化到寄存器中
        obj *volatile *my_free_list;
        obj *result;

        if(n > (size_t)__MAX_BYTES){
            return (malloc_alloc::allocate(n));
        }
        my_free_list = free_list + FREELIST_INDEX(n);
        result = *my_free_list;
        if(0 == result){
            //没有找到freelist
            void *r = refill(ROUND_UP(n));
            return r;
        }
        *my_free_list = result->free_list_link;
        return result;
    }
    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);
        //让my_free_list往前指一个单位,q->next = p; p = q;
        q->free_list_link = *my_free_list;
        *my_free_list = q;
    }
    static void *reallocate(size_t n);
private:
    static size_t ROUND_UP(size_t n)
    {
        //如果二进制的后三位不为0,那么就去进位,然后和7的反相与,去除后三位
        //是8的倍数
        return (n+__ALIAN-1) & ~(__ALIAN-1);
    }
private:
    union obj
    {
        union obj *free_list_link; //指向下一个节点
        char first_block[1];       //下一个节点的首地址的内容
    };
private:
    static obj *volatile free_list[__NFREELISTS];
    static size_t FREELIST_INDEX(size_t n)
    {
        return ROUND_UP(n) / __ALIAN - 1;
    }
    //free_list没有可用块时调用refill
    static void *refill(size_t n)
    {
        int nobjs = 20;

        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;
        //引导free list指向新配置的空间
        *my_free_list = next_obj = (obj *)(chunk + n);
        //不断的去挂链
        for(i = 0; ; ++i){
            current_obj = next_obj;
            next_obj = (obj *)(next_obj + n);
            //最后一块就让它置空
            if(nobjs - 1 == i){
                current_obj->free_list_link = 0;
                break;
            }
            current_obj->free_list_link = next_obj;
        }

        return result;
    }
    //配置一大块
    static char *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 = start_free;
            start_free += total_bytes;
            return result;
        }else if(bytes_left >= size){
            nobjs = bytes_left / size;
            total_bytes = nobjs * size;
            result = start_free;
            start_free += size;
            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);
                *my_free_list = (obj *)start_free;
            }
            start_free = (char *)malloc(bytes_to_get);
            if(0 == start_free){
                int i;
                obj *volatile *my_free_list, *p;
                //去空闲链表找
                for(i = size; i < __MAX_BYTES; i += __ALIAN){
                    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));
                    }
                }
                //还没找到
                end_free = 0;
                start_free = (char *)malloc_alloc::allocate(bytes_to_get);
            }
            heap_size += bytes_to_get;
            end_free  = start_free + bytes_to_get;
            return chunk_alloc(size, nobjs);
        }
    }

    static char *start_free;
    static char *end_free;
    static size_t heap_size;
};
template <bool threads, int inst>
char *__default_alloc_template<threads, inst> :: start_free = 0;
template <bool threads, int inst>
char *__default_alloc_template<threads, inst> :: end_free = 0;
template <bool threads, int inst>
size_t __default_alloc_template<threads, inst> :: heap_size = 0;
//为什么要写两次
template <bool threads, int inst>
//obj *volatile是类型,并且该类型属于类中
//typename __default_alloc_template<threads, inst> :: obj* volatile
union a
{
};
a *volatile __default_alloc_template<threads, inst> :: free_list[__NFREELISTS] =
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
typedef __default_alloc_template<0,0> alloc;


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值