_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)(),直到内存分配成功(他认为用户的处理函数总会获取到可用的内存)。