STL的二级空间配置器类似于memory_pool,但是并不是memory_pool,因为STL的二级空间配置器只维护了16个free_list,而且只是小于128byte大小的小区块,大于128byte会调用一级空间配置器malloc。16个链表放在数组中,每一个数组所链的链表代表大小相等的小区块,因为分配的时候,只会申请8的倍数大小的空间,所以小于128byte的区块肯定是属于free_list里面。当小区块释放时,就会挂到free_list中。
下面是二级空间配置器的源码:
template <bool threads, int inst>class __default_alloc_template
_ALIGN = 8,表示最小的小区块的大小 _MAX_BYTES = 128 表示最大的区块的大小 _NFREELISTS = 16 表示free_list的个数
#if ! (defined(__SUNPRO_CC) || defined(__GNUC__)) enum {_ALIGN = 8}; enum {_MAX_BYTES = 128}; enum {_NFREELISTS = 16}; // _MAX_BYTES/_ALIGN# endif
这是将申请的大小按8字节对齐:
static size_t _S_round_up(size_t __bytes) { return (((__bytes) + (size_t) _ALIGN-1) & ~((size_t) _ALIGN - 1)); }
这是free_list的Node结构体:
__PRIVATE: union _Obj { union _Obj* _M_free_list_link; char _M_client_data[1]; /* The client sees this. */ };
free_list的定义:
# if defined(__SUNPRO_CC) || defined(__GNUC__) || defined(__HP_aCC) static _Obj* __STL_VOLATILE _S_free_list[]; // Specifying a size results in duplicate def for 4.1# else static _Obj* __STL_VOLATILE _S_free_list[_NFREELISTS]; //volatile 是为了防止在多线程下产生不可预期的后果# endif
内存池的相关信息:
static char* _S_start_free; //内存池中未分给free_pool的首地址 static char* _S_end_free; //内存池的末尾地址 static size_t _S_heap_size;//内存池的总空间大小
二级空间配置器分配函数:
static void* allocate(size_t __n) { void* __ret = 0; //如果size大于128byte就使用一级配置器 if (__n > (size_t) _MAX_BYTES) { __ret = malloc_alloc::allocate(__n); } else { _Obj* __STL_VOLATILE* __my_free_list = _S_free_list + _S_freelist_index(__n); //找到size对应的list // Acquire the lock here with a constructor call. // This ensures that it is released in exit or during stack // unwinding.# ifndef _NOTHREADS /*REFERENCED*/ _Lock __lock_instance;//有关线程安全# endif _Obj* __RESTRICT __result = *__my_free_list; //指向size所在链表头 if (__result == 0) __ret = _S_refill(_S_round_up(__n)); //当这个链表为空时,重新创建一个小chunk else { *__my_free_list = __result -> _M_free_list_link; //将链第一个节点取下来 __ret = __result; //给用户返回取下来的小区块的地址 } } return __ret; };
下面的函数是从一级空间配置器(malloc)申请小块内存的函数:
templatevoid*__default_alloc_template<__threads, __inst>::_S_refill(size_t __n){ int __nobjs = 20; /*默认的建议值,在申请小内存块的时候,一次申请的个数,这并不是最终个数,会根据内存池剩下的空间大小进行调整*/ char* __chunk = _S_chunk_alloc(__n, __nobjs);//从内存池中申请_nobjs个_n大小的空间 _Obj* __STL_VOLATILE* __my_free_list; //指向free_list的指针 _Obj* __result; _Obj* __current_obj; _Obj* __next_obj; int __i; /*如果在_S_chunk_alloc中只申请到了一块这样大的空间,则直接返回*/ if (1 == __nobjs) return(__chunk); __my_free_list = _S_free_list + _S_freelist_index(__n); //找到该大小所对应的free_list /*将得到的多块_n大小的chunk,第一块返回给用户,剩下的加到_n所对应的free_list中*/ __result = (_Obj*)__chunk; *__my_free_list = __next_obj = (_Obj*)(__chunk + __n); for (__i = 1; ; __i++) { __current_obj = __next_obj; __next_obj = (_Obj*)((char*)__next_obj + __n); if (__nobjs - 1 == __i) { __current_obj -> _M_free_list_link = 0; break; } else { __current_obj -> _M_free_list_link = __next_obj; } } return(__result);}
_S_chunk_alloc:
templatechar*__default_alloc_template<__threads, __inst>::_S_chunk_alloc(size_t __size, int& __nobjs){ char* __result; size_t __total_bytes = __size * __nobjs; //想要开辟的总大小 size_t __bytes_left = _S_end_free - _S_start_free; //内存池的剩余大小/*判断内存池剩余的空间和需要申请的空间的大小比较*/ if (__bytes_left >= __total_bytes) { __result = _S_start_free; //大于就可以直接分配 _S_start_free += __total_bytes; return(__result); } else if (__bytes_left >= __size) { __nobjs = (int)(__bytes_left/__size);//调整需要申请的块数 //使用内存池剩余空间分配大小为_size的内存块,能分多少块就分多少块 __total_bytes = __size * __nobjs; __result = _S_start_free; _S_start_free += __total_bytes; //将内存池的start往下移,分配空间 return(__result); } else { size_t __bytes_to_get = 2 * __total_bytes + _S_round_up(_S_heap_size >> 4); //内存吃没有足够的空间分配时,需要重新申请空间,这里的size就是需要向一级空间配置器申请的空间大小 if (__bytes_left > 0) { _Obj* __STL_VOLATILE* __my_free_list = _S_free_list + _S_freelist_index(__bytes_left);//将内存池剩余的小于_size的空间挂到对应大小的free_list上。 ((_Obj*)_S_start_free) -> _M_free_list_link = *__my_free_list; *__my_free_list = (_Obj*)_S_start_free; } /*内存池剩余为0时,malloc一个小块内存,进行分配*/ _S_start_free = (char*)malloc(__bytes_to_get); if (0 == _S_start_free) { size_t __i; _Obj* __STL_VOLATILE* __my_free_list; _Obj* __p; /*如果这是第一次申请内存池空间,则进行循环分配小块空间*/ 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; _S_start_free = (char*)__p; _S_end_free = _S_start_free + __i; return(_S_chunk_alloc(__size, __nobjs)); } } //将申请的空间分配完之后,再申请一块内存池空间作为备用 _S_end_free = 0; _S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get); } /*如果不是第一次申请内存池空间,把内存池的大小扩大,再调用自己进行空间分配*/ _S_heap_size += __bytes_to_get; _S_end_free = _S_start_free + __bytes_to_get; return(_S_chunk_alloc(__size, __nobjs)); }}
这就是整个STL库中的二级空间配置器的流程,其实也在模仿ptmalloc的内存管理机制,使用内存池和空间回收,避免多次库函数调用或者系统调用,大大节省了程序运行的时间。这里的二级空间配置器只要用到了free_list和小块内存池,双层保证小块空间分配的速率比直接malloc快。但是在实战中的效率对比还是需要实践才能得出结论。