为了进一步了解
Allocator的运作原理,我们以下面的使用作为例子,解读其实现细节。
std::allocator<int>MyAll;
MyAll.allocate(10);
以上代码调用SGI STL的allocator,分配10个int型的空间。下面的分析使用SGISTL源代码allocator.cpp,并加上我的理解注释,建议参考侯捷的书做了解。但我目前用的实作版本和侯捷用的版本有小差异,包括使用的函数名称前面大部分加了__S_标志,内存块管理新版本使用了栈的方式管理(尽管目前实际上最多只有一个元素,以后可能会扩展到多个)不过对理解原理影响不大。
STL接口实现的std::allocator类会把需要分配的空间计算好sizeof(int)*4=40然后调用节点分配的以下函数来实现。
根据提供的参数__n(即申请的内存空间)来决定用一级分配器还是二级分配器。在STL的实现中,微软以及SGI的实现中_MAX_BYTES都取的是128。
static void* _STLP_CALL allocate(size_t& __n)
{ return (__n > (size_t)_MAX_BYTES) ? __stl_new(__n) : _M_allocate(__n); }
因为大于128的情况都是直接使用newoperator直接申请内存,没有什么值得说的地方;在小于等于128的情况使用二级分配器能有减少内存碎片,提高内存利用率的效果。我们以下讨论的都是小于或等于128的情况。
STL把维护一个16个成员_S_free_list数组,分别管理从8到128个字节的空间链表,当需要申请1-128个字节的空间时,会查找相应的链表,如果有节点就直接分配返还给程序使用,如果链表不够就使用_S_refill向大块内存空间申请。
void* __node_alloc_impl::_M_allocate(size_t& __n) {
__n = _S_round_up(__n);//把__n上调到8的倍数,以减少内存碎片
_Obj* __r = _S_free_list[_S_FREELIST_INDEX(__n)].pop();//查找对应链表是否有可用节点
if (__r == 0)
{ __r = _S_refill(__n); }//如果没有可用节点,则从大块内存空间申请并返回(如果多申请的空间放到这个链表中作后备使用)。
return __r;
}
/* Returns an object of size __n, and optionally adds additional ones to */
/* freelist of objects of size __n. */
/* We assume that __n is properly aligned. */
__node_alloc_impl::_Obj* __node_alloc_impl::_S_refill(size_t __n) {//这个时候__n是40
int __nobjs = 20;//默认分配20倍所需要的节点大小的空间
char* __chunk = _S_chunk_alloc(__n, __nobjs);//向大块内存去申请:大哥,我需要20个__n大小的空间来做事情,最好分20个,如果不够,能分多少给我多少。
if (__nobjs <= 1)
return __REINTERPRET_CAST(_Obj*, __chunk);//别说20个,现在是连一个都分配不到的情况!
// Push all new nodes (minus first one) onto freelist
_Obj* __result = __REINTERPRET_CAST(_Obj*, __chunk);//把分配的第一个节点空间记录好作为返还的东西。
_Obj* __cur_item = __result;
_Freelist* __my_freelist = _S_free_list + _S_FREELIST_INDEX(__n);
//多余的空间全部放到对应的链表中以备使用。这里是放到大小是40bytes的那条链上去保存了。
for (--__nobjs; __nobjs != 0; --__nobjs) {
__cur_item = __REINTERPRET_CAST(_Obj*, __REINTERPRET_CAST(char*, __cur_item) + __n);
__my_freelist->push(__cur_item);
}
return __result;
}
/* We allocate memory in large chunks in order to avoid fragmenting */
/* the malloc heap too much. */
/* We assume that size is properly aligned. */
// 去问大块内存申请20个40bytes大小的空间!这里_p_size是40,__nobjs是20
char* __node_alloc_impl::_S_chunk_alloc(size_t _p_size, int& __nobjs) {
char* __result = 0;
__add_atomic_t __total_bytes = __STATIC_CAST(__add_atomic_t, _p_size) * __nobjs;
//内存块也维护着一个内存池链表_S_free_mem_blocks,其实它只维护一块,有时候连一块都没有。
_FreeBlockHeader* __block = __STATIC_CAST(_FreeBlockHeader*, _S_free_mem_blocks.pop());
if (__block != 0) {
// We checked a block out and can now mess with it with impugnity.
// We'll put the remainder back into the list if we're done with it below.
char* __buf_start = __REINTERPRET_CAST(char*, __block);
//我们算一下,这个内存块还剩下__bytes_left空间。
__add_atomic_t __bytes_left = __block->_M_end - __buf_start;
//需要比较一下能不能满足我们的40*20的需要。
if ((__bytes_left < __total_bytes) && (__bytes_left >= __STATIC_CAST(__add_atomic_t, _p_size))) {
// 哈,我就这么多了,给你一个40是足够的,但是到不了20个40bytes!
__result = __buf_start; //注意,已经分给空间了
__nobjs = (int)(__bytes_left/_p_size);
__total_bytes = __STATIC_CAST(__add_atomic_t, _p_size) * __nobjs;
__bytes_left -= __total_bytes;
__buf_start += __total_bytes;
}
else if (__bytes_left >= __total_bytes) {
// The block has enough left to satisfy all that was asked for
// 哥的池里东西多的很,足够你20个40bytes的空间,你拿去用吧!
__result = __buf_start; //注意,已经分给空间了
__bytes_left -= __total_bytes;
__buf_start += __total_bytes;
}
if (__bytes_left != 0) {//内存块还有剩余(不管是不是连一个40都分不到还是分完了20个40bytes还有多)
// There is still some memory left over in block after we satisfied our request.
/* 已经分给空间了,但是还有多(因为维护内存块链表的需要,留下的块需要大于内存块链表维护的结构才行,具体的机理是一个节省内存的小技巧,在侯先生的书上有说明),可以放回到内存块链表中。*/
if ((__result != 0) && (__bytes_left >= (__add_atomic_t)sizeof(_FreeBlockHeader))) {
// We were able to allocate at least one object and there is still enough
// left to put remainder back into list.
_FreeBlockHeader* __newblock = __REINTERPRET_CAST(_FreeBlockHeader*, __buf_start);
__newblock->_M_end = __block->_M_end;
_S_free_mem_blocks.push(__newblock);
}
else {
// We were not able to allocate enough for at least one object.
// Shove into freelist of nearest (rounded-down!) size.
// 没有分给空间,或者留下的空间小于内存块链表维护的结构大小(在32位机器上,这个维护需要的大小是两个指针共8bytes,64位机为16bytes了),就把这块小内存分配给对应大小的自由链表。
size_t __rounded_down = _S_round_up(__bytes_left + 1) - (size_t)_ALIGN;
if (__rounded_down > 0)
_S_free_list[_S_FREELIST_INDEX(__rounded_down)].push((_Obj*)__buf_start);
}
}
if (__result != 0)
return __result;//分配过了的话,就返回吧!
}
// We couldn't satisfy it from the list of free blocks, get new memory.
/* 如果还没分配过的话,现在开始做分配的工作了,狮子开大口,比两倍还要多。先要多一点过来,避免经常要问系统要内存。这里的_S_heap_size是一个调整的递增微量,是上一次分配的时候用上一次的__bytes_to_get >> 4加上得到的值(下面可以看到这个计算),分配次数越多,这个值就会自动调整得越大,让你一次分多一点,尽量减少分配次数。一开始的话_S_heap_size的值为0的。*/
__add_atomic_t __bytes_to_get = 2 * __total_bytes +
__STATIC_CAST(__add_atomic_t,
_S_round_up(__STATIC_CAST(__uadd_atomic_t, _STLP_ATOMIC_ADD(&_S_heap_size, 0)))) +
_STLP_OFFSET;
_STLP_TRY {
__result = __stlp_new_chunk(__bytes_to_get);//这里会直接调用operator new分配滴!
}//这里省略了不影响理解思路的内存分配错误捕获函数段。
_STLP_ATOMIC_ADD(&_S_heap_size, __bytes_to_get >> 4);
if (__bytes_to_get > __total_bytes) {
// Push excess memory allocated in this chunk into list of free memory blocks
// 分得多了,把余下的内存放到内存块链表管理起来,以备下一次使用。
_FreeBlockHeader* __freeblock = __REINTERPRET_CAST(_FreeBlockHeader*, __result + __total_bytes);
__freeblock->_M_end = __result + __bytes_to_get;
_S_free_mem_blocks.push(__freeblock);//我上面说过,它最多也就维护一份内存块。
}
return __result;
}