跳至主要內容

实际分配内存池_S_chunk_alloc源码

张威大约 8 分钟c/c++SGI STL源码

实际分配内存池_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字节的时候:

  1. 若二级空间配置器应用管理的剩余内存还20*size个字节,则直接分出去20*size专门用来分配20个size字节的chunk,然后需要在_S_refill函数中填写next域

  2. 若不够分配20*size个字节

    1. 但是够分配size个字节,那将备用的内存尽可能多的分割出完整的size内存块(修改了__nobjs大小)。此时也需要在_S_refill函数中填写next域
    2. 然而当备用内存只能分割出一个size内存块时,就不用再填写地址域了。其实也没有办法再填写了,因为没内存了。
  3. 若实在太小,连一个size内存块也分配不出来。剩余的备用内存就会根据自身的大小被挂在_S_free_list 的对应位置,然后需要重新找OS申请新的内存(malloc)

    1. 向OS申请成功,则分配一个大块的空闲内存,并返回__size*__nobjs大小的内存用于分配
    2. 向OS申请失败则
      1. _S_free_list中查找一个比__size大的空闲chunk分配出去,然后再回到之前的分配流程分配出去。
      2. 如果在_S_free_list中不存在比__size大的空闲chunk,则再次尝试正常申请内存(malloc)
        1. 申请成功,则返回正常分配
        2. 申请失败,先检查时候设置用于释放某些可以释放的资源的回调函数,
          1. 如果没有设置,则抛出异常
          2. 若有设置则调用回调函数释放资源,然后再次malloc分配
            1. 若分配成功则返回正常分配
            2. 若失败则循环调用回调函数释放资源然后malloc分配,直至分配成功

进入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);
    }
}