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)申请小块内存的函数:
template <bool __threads, int __inst>
void*
__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:
template <bool __threads, int __inst>
char*
__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快。但是在实战中的效率对比还是需要实践才能得出结论。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。