实际分配内存池_S_chunk_alloc源码
大约 8 分钟
实际分配内存池_S_chunk_alloc源码
_S_chunk_alloc(size_t __size, int& __nobjs)
作用:到备用内存中拿__nobjs
个__size
字节的chunk块分配
如果备用内存足够,就从备用内存中拿,然后将_S_start_free
指向分配后,空闲备用空间的首地址
如果备用内存不够了,就malloc申请,并让_S_start_free
指向malloc的首地址,让_S_end_free
指向为malloc空间的尾地址,然后递归调用_S_chunk_alloc
重新在备用内存中拿__nobjs个__size字节的chunk块分配
有一个疑问:为什么其他申请的16B的内存可以在_S_free_list 的8B的内存池申请 后来我懂了,源码中用 static char* _S_end_free、_S_start_free变量,标识备用内存池的范围。也就是不管分配多少字节的内存,都是在_S_start_free和_S_end_free区间内给用户分配。_S_free_list的每个元素都是一个指针,保存的是专门为分配对应字节的一大块内存空闲位置的起始地址

再到备用内存给用户分配size字节的时候:
若二级空间配置器应用管理的剩余内存还够
20*size
个字节,则直接分出去20*size
专门用来分配20个size字节的chunk,然后需要在_S_refill
函数中填写next域若不够分配
20*size
个字节- 但是够分配size个字节,那将备用的内存尽可能多的分割出完整的size内存块(修改了__nobjs大小)。此时也需要在
_S_refill
函数中填写next域, - 然而当备用内存只能分割出一个size内存块时,就不用再填写地址域了。其实也没有办法再填写了,因为没内存了。
- 但是够分配size个字节,那将备用的内存尽可能多的分割出完整的size内存块(修改了__nobjs大小)。此时也需要在
若实在太小,连一个size内存块也分配不出来。剩余的备用内存就会根据自身的大小被挂在
_S_free_list
的对应位置,然后需要重新找OS申请新的内存(malloc)- 向OS申请成功,则分配一个大块的空闲内存,并返回
__size*__nobjs
大小的内存用于分配 - 向OS申请失败则
- 在
_S_free_list
中查找一个比__size
大的空闲chunk分配出去,然后再回到之前的分配流程分配出去。 - 如果在
_S_free_list
中不存在比__size
大的空闲chunk,则再次尝试正常申请内存(malloc)- 申请成功,则返回正常分配
- 申请失败,先检查时候设置用于释放某些可以释放的资源的回调函数,
- 如果没有设置,则抛出异常
- 若有设置则调用回调函数释放资源,然后再次malloc分配
- 若分配成功则返回正常分配
- 若失败则循环调用回调函数释放资源然后malloc分配,直至分配成功
- 在
- 向OS申请成功,则分配一个大块的空闲内存,并返回
进入if中:

进入else中:

template <bool __threads, int __inst>
char*
__default_alloc_template<__threads, __inst>::_S_chunk_alloc(size_t __size,
int& __nobjs) // 按引用接收__nobjs,如果不够__nobjs会改变该值的大小
{
char* __result;//指向待分配的内存首地址
// 实际分配的字节数,8 * 20
size_t __total_bytes = __size * __nobjs; //需要返回的内存大小
//_S_end_free、_S_start_free都是static char*变量,标识备用内存池的范围,初始值为0
// _S_start_free、_S_end_free分别记录内存池(__size * __nobjs)可用的起始和末尾地址
size_t __bytes_left = _S_end_free - _S_start_free;//备用内存中可用内存大小
if (__bytes_left >= __total_bytes) {//完全足够,直接分配
// 正常的__size在对应的内存池分配,备用的内存池还可以继续分配__size * __nobjs
// 可以分配出去一个小的内存池
__result = _S_start_free;
// 修改_S_start_free,分配__total_bytes空间出去
_S_start_free += __total_bytes;//_S_start_free指向剩余可以内存首地址
return(__result);
} else if (__bytes_left >= __size) {
// 注意这里为什么是大于等于一个chunk块的大小__size,因为容器申请只是申请一个内存块
// 而是空间配置器程序调用_S_chunk_alloc希望能够分配20个chunk块的内存,可以不满足分配20个块的要求,但必须满足容器申请1个内存块的要求,所以可以修改__nobjs的值
// 假设我们向内存池申请16字节的时候,我们在_S_free_list向OS申请8字节时备用的内存池进行申请
// 此时会进入else if
// 剩余的字节数还能分配出__nobjs个完整的__size的__chunk块
__nobjs = (int)(__bytes_left/__size);
// 现在的__total_bytes 肯定不大于 _S_end_free - _S_start_free
__total_bytes = __size * __nobjs;
// __result 指向空闲内存池的首地址,也就是即将被分配出去的首地址
__result = _S_start_free;
// 更新空闲内存池的首地址
_S_start_free += __total_bytes;
return(__result);
} else {
// 没有足够的空间分配了 或者_S_free_list还没有申请过内存池 ,需要申请内存
// _S_heap_size初始值为0,第一次向OS申请的话__bytes_to_get = 2 * 8 * 20 = 320
// _S_heap_size记录了已经向OS申请的所有空间
size_t __bytes_to_get = 2 * __total_bytes + _S_round_up(_S_heap_size >> 4);//所以,新申请的内存只会越来越大
// Try to make use of the left-over piece.
if (__bytes_left > 0) {//原来备用内存有剩余,但是不足size的情况
// 由于之前申请的备用内存太小,甚至无法分出一个size
// 这里剩余的内存必然为8的整数倍,且在区间[8,128)内。因为从OS申请空间的时候就是按照8的整数申请的
// 之前申请的备用内存,会被挂在__my_free_list后,专门用于分配指定大小的内存
// 而且只能分配 <指定大小的内存> 一次
//找到__bytes_left大小的空间在自由链表的位置,因为元素为指针,所以遍历用二级指针
_Obj* __STL_VOLATILE* __my_free_list =
_S_free_list + _S_freelist_index(__bytes_left);
// 剩余内存连接到_S_freelists上
((_Obj*)_S_start_free) -> _M_free_list_link = *__my_free_list;
*__my_free_list = (_Obj*)_S_start_free;
}
// 用malloc申请__bytes_to_get个字节,也就是一个小的内存池了,_S_start_free 指向小内存池的首地址
_S_start_free = (char*)malloc(__bytes_to_get);
if (0 == _S_start_free) {
// 向OS申请内存失败
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.
// 1. 剩余的备用内存连一个__size的空间都不够,2. 向OS申请__bytes_to_get字节内存失败
// __i从__size开始到128(_MAX_BYTES)在_S_free_list中从前往后找挂载的空闲内存块
for (__i = __size; __i <= (size_t) _MAX_BYTES; __i += (size_t) _ALIGN) {
//当前__i大小的chunk应该在_S_free_list挂载的位置
__my_free_list = _S_free_list + _S_freelist_index(__i);
__p = *__my_free_list;//表示挂载第一个chunk的地址
if (0 != __p) {//如果当前位置存在挂载的chunk的情况
// 找到_S_free_list中已经分配内存池的位置
// 修改_S_free_list的对应位置的地址,准备分配出一整个__i块出去
*__my_free_list = __p -> _M_free_list_link;//指向第二个chunk地址
// 在_S_free_list别的地方拿到未分配的内存块后,修改_S_start_free、_S_end_free
// 得到新的备用内存,重新给用户分配__nobjs个__size的空间
_S_start_free = (char*)__p;
_S_end_free = _S_start_free + __i;
return(_S_chunk_alloc(__size, __nobjs));
// Any leftover piece will eventually make it to the right free list.
}
}
// 若for循环完成,依然找不到空闲的内存块
_S_end_free = 0; // In case of exception.
// 重新再次申请__bytes_to_get字节
_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 记录所有申请的堆空间大小
_S_heap_size += __bytes_to_get;
// _S_end_free 指向空闲小内存池的尾地址
_S_end_free = _S_start_free + __bytes_to_get;
//
return(_S_chunk_alloc(__size, __nobjs));
}
}
malloc_alloc::allocate源码
// 可以设置一个回调函数,当OS开辟内存失败的时候,调用此函数。
// 此函数可能用于释放某些可以释放的资源
#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
static void (* __malloc_alloc_oom_handler)();
#endif
static void* malloc_alloc::allocate(size_t __n){
// 再次尝试一下正常向OS申请空间
void* __result = malloc(__n);
// 正常向OS申请空间失败,调用_S_oom_malloc
if (0 == __result) __result = _S_oom_malloc(__n);
return __result;
}
template <int __inst>
void* __malloc_alloc_template<__inst>::_S_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);
}
}
