SGI_STL空间配置器源码剖析(五)_S_chunk_alloc函数、oom和优点

_S_chunk_alloc函数是操作自由链表分配小内存、内存不够时还会调用开辟内存函数,个人认为是空间配置器源码中最精华的一个函数,其思想真是精辟!

_S_chunk_alloc代码及解析如下:

/* We allocate memory in large chunks in order to avoid fragmenting     */
/* the malloc heap too much.                                            */
/* We assume that size is properly aligned. __size 已经向上临近8        */
/* We hold the allocation lock.                                         */
template <bool __threads, int __inst>
char*
__default_alloc_template<__threads, __inst>::_S_chunk_alloc(size_t __size, 
                                                            int& __nobjs) //__nobjs默认20,可修改
{
    char* __result;
    size_t __total_bytes = __size * __nobjs;
    size_t __bytes_left = _S_end_free - _S_start_free; // 剩余字节数,可以分配给不同大小的chunk块

    if (__bytes_left >= __total_bytes) { // 剩余已申请的空间中足够分配20个大小为size的chunk块
        __result = _S_start_free;
        _S_start_free += __total_bytes;
        return(__result);
    } else if (__bytes_left >= __size) { // 不够的话,计算够分配几个的,更改分配个数
        __nobjs = (int)(__bytes_left/__size);
        __total_bytes = __size * __nobjs;
        __result = _S_start_free;
        _S_start_free += __total_bytes;
        return(__result);
    } else { // 实在不够用,先处理完剩余小内存,再分配新空间
        size_t __bytes_to_get = 
	  2 * __total_bytes + _S_round_up(_S_heap_size >> 4); // _S_heap_size除以16 再向上取8的倍数
        // Try to make use of the left-over piece.
        // 此处剩余量不够本次的一个chunk块,就将剩余的字节作为一个chunk块放给合适他的链表头部,即充分利用每一块小内存
        if (__bytes_left > 0) {
            _Obj* __STL_VOLATILE* __my_free_list =
                        _S_free_list + _S_freelist_index(__bytes_left);

            ((_Obj*)_S_start_free) -> _M_free_list_link = *__my_free_list;
            *__my_free_list = (_Obj*)_S_start_free;
        }
        _S_start_free = (char*)malloc(__bytes_to_get);
        if (0 == _S_start_free) {
            size_t __i;
            _Obj* __STL_VOLATILE* __my_free_list;
	    _Obj* __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.
            // 系统空间不够,则查看数组右侧更大chunk链表中有无空闲chunk
            // for循环从当前分配不了的块大小(假设为40)往右遍历数组
            // _p对右边第一个有空闲块的链表(假设为48)进行遍历
            // 然后取其头部块,大小为48,作为40的start和end,原48的链表删除此节点
            // start!=end 即为有空闲字节,可以继续递归调用本函数
            for (__i = __size;
                 __i <= (size_t) _MAX_BYTES;
                 __i += (size_t) _ALIGN) {
                __my_free_list = _S_free_list + _S_freelist_index(__i); 
                __p = *__my_free_list;
                if (0 != __p) { //
 链表不为空
                    *__my_free_list = __p -> _M_free_list_link; // 指向第一个chunk块
                    _S_start_free = (char*)__p;
                    _S_end_free = _S_start_free + __i;
                    return(_S_chunk_alloc(__size, __nobjs)); // 获取到了48字节,但是本来要40字节,故递归调用
                    // Any leftover piece will eventually make it to the
                    // right free list.
                }
            }
	    _S_end_free = 0;	// In case of exception.
	    	// 若右侧大chunk都没有空闲:
            _S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get); //真正开辟内存
            // This should either throw an
            // exception or remedy the situation.  Thus we assume it
            // succeeded.
        }
        _S_heap_size += __bytes_to_get;
        _S_end_free = _S_start_free + __bytes_to_get;
        return(_S_chunk_alloc(__size, __nobjs)); // 第一次构造内存池申请20倍的内存时/无空节点时 递归调用一次,准备好freeStart/End
    }
}

示意图:

函数在执行任务分配空间时:

先看剩余已申请的空间中是否足够分配20个大小为size的chunk块;

不够的话,计算够分配几个的,更改分配个数;

实在不够用,先处理完剩余小内存,再分配新空间:

        1、剩余量不够本次的一个chunk块,就将剩余的字节作为一个chunk块放给合适他的链表头部,即充分利用每一块小内存

        2、查看数组右侧更大chunk链表中有无空闲chunk

        3、万不得已,才调用malloc_alloc::allocate开辟一段20倍的新空间。

可以发现,对内存的利用率极高,而且代码写的非常简洁!C++博大精深~~

oom回调函数

预先设置好的malloc内存分配失败以后的回调函数,以下是声明部分:

template <int __inst>
class __malloc_alloc_template {

private:

  static void* _S_oom_malloc(size_t);
  static void* _S_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)
  {
    void* __result = malloc(__n);
    if (0 == __result) __result = _S_oom_malloc(__n);
    return __result;
  }

  static void (* __set_malloc_handler(void (*__f)()))()
  {
    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>
void (* __malloc_alloc_template<__inst>::__malloc_alloc_oom_handler)() = 0; // 函数指针类型
#endif

template <int __inst>
void*
__malloc_alloc_template<__inst>::_S_oom_malloc(size_t __n) // out of mamery
{
    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); // 死循环,直到调用成功,内存分配成功
    }
}

SGI STL二级空间配置器内存池的实现优点:

1.对于每一个字节数的chunk块分配,都是给出一部分进行使用,另一部分作为备用,这个备用可以给当前字节数使用,也可以给其它字节数使用

2.对于备用内存池划分完chunk块以后,如果还有剩余的很小的内存块,再次分配的时候,会把这些小的内存块再次分配出去,备用内存池使用的干干净净

3.当指定字节数内存分配失败以后,有一个异常处理的过程,遍历从bytes字节到128字节所有的chunk块列表进行查看,如果哪个字节数有空闲的chunk块,直接借一个出去

如果上面操作失败,还会调用预先设置好的 malloc内存分配失败以后的 回调函数oom_malloc函数。如果用户没设置回调函数,则malloc会throw bad alloc;如果设置了,则for(;;) 死循环执行用户的处理函数(*oom malloc handler)(),直到内存分配成功(他认为用户的处理函数总会获取到可用的内存)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值