SGI STL 源码解读之Allocator

为了进一步了解 Allocator的运作原理,我们以下面的使用作为例子,解读其实现细节。

     std::allocator<int>MyAll;

     MyAll.allocate(10);

     以上代码调用SGI STLallocator,分配10int型的空间。下面的分析使用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数组,分别管理从8128个字节的空间链表,当需要申请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;
}


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值