Chinaunix首页 | 论坛 | 博客
  • 博客访问: 259023
  • 博文数量: 21
  • 博客积分: 1263
  • 博客等级: 准尉
  • 技术积分: 697
  • 用 户 组: 普通用户
  • 注册时间: 2012-03-24 00:05
个人简介

专注于Answers Ranking, Answer Monitor和log处理。

文章分类
文章存档

2014年(5)

2012年(16)

分类: C/C++

2012-04-11 08:10:24

STL源代码剖析-04 stl_alloc.h(1) 中讲了大额内存(大于128byte)是怎么分配的现在主要来看≤128bytes是怎么做的。先看三个枚举常量:

  1. enum {_ALIGN = 8};
  2.   enum {_MAX_BYTES = 128};
  3.   enum {_NFREELISTS = 16}; // _MAX_BYTES/_ALIGN

分配小内存是通过己申请的内存进行分配的,它通过16(_NFREELISTS)个链表来管理申请的内存。而且分配的倍数是8(_ALIGN)的倍数,就算你申请1byte它也会给你分配8byte。16*8=128(_MAX_BYTES)。所以可以看到这16个链表节点分别是大小为8,16,24,... ... ,128bytes.

  1. union _Obj {
  2.         union _Obj* _M_free_list_link;
  3.         char _M_client_data[1]; /* The client sees this. */
  4.   };
这就是链表结构了,可以看到它是union类型,这主要是为了结省空间,即当空间不被使用时就是指针,否则就是存储数据。再看它的数据成员:

  1. template <bool __threads, int __inst>
  2. char* __default_alloc_template<__threads, __inst>::_S_start_free = 0;

  3. template <bool __threads, int __inst>
  4. char* __default_alloc_template<__threads, __inst>::_S_end_free = 0;

  5. template <bool __threads, int __inst>
  6. size_t __default_alloc_template<__threads, __inst>::_S_heap_size = 0;

  7. template <bool __threads, int __inst>
  8. typename __default_alloc_template<__threads, __inst>::_Obj* __STL_VOLATILE
  9. __default_alloc_template<__threads, __inst> ::_S_free_list[
  10. # if defined(__SUNPRO_CC) || defined(__GNUC__) || defined(__HP_aCC)
  11.     _NFREELISTS
  12. # else
  13.     __default_alloc_template<__threads, __inst>::_NFREELISTS
  14. # endif
  15. ] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
可以看到初始值都是10,并且链表数据的指针都是NULL,现在就来看看主要部分,分配内存和重新分配内存是怎么做的:

  1. /* __n must be > 0 */
  2.   static void* allocate(size_t __n)
  3.   {
  4.     void* __ret = 0;

  5.     if (__n > (size_t) _MAX_BYTES) {
  6.       __ret = malloc_alloc::allocate(__n);
  7.     }
  8.     else {
  9.       _Obj* __STL_VOLATILE* __my_free_list
  10.           = _S_free_list + _S_freelist_index(__n);
  11.       // Acquire the lock here with a constructor call.
  12.       // This ensures that it is released in exit or during stack
  13.       // unwinding.
  14. # ifndef _NOTHREADS
  15.       /*REFERENCED*/
  16.       _Lock __lock_instance;
  17. # endif
  18.       _Obj* __RESTRICT __result = *__my_free_list;
  19.       if (__result == 0)
  20.         __ret = _S_refill(_S_round_up(__n));
  21.       else {
  22.         *__my_free_list = __result -> _M_free_list_link;
  23.         __ret = __result;
  24.       }
  25.     }

  26.     return __ret;
  27.   };
首先它看要分配的大小__n,如果大于_MAX_BYTES就使用malloc_alloc来分配,就是上一节讲的那个内存分配类
  1. typedef __malloc_alloc_template<0> malloc_alloc;
 如果不是,先找到那个链表的下标:
  1. static size_t _S_freelist_index(size_t __bytes) {
  2.         return (((__bytes) + (size_t)_ALIGN-1)/(size_t)_ALIGN - 1);
  3.   }
因为下标是0开始的,所以最后还要-1。如要分配是22byte,因为实际应该分配24(3*8)byte,它的链表下标是2(0-8,1-16,2-24),所以先是(22+7)/ 8 - 1=2. my_free_list 就是那个地方了,如果它不为0,就是这个链表不为空,则将最前面结点(不一定是一个结点,应该是一块内存,大小为24byte)分配出来作为分配的内存,并删除这个结点(一块内存,即从链表中删除它),如果不够,就调用_S_refill(_S_round_up(__n))来分配。_S_round_up(__n)是把__n作为最接近__n的8的倍数。

  1. static size_t
  2.   _S_round_up(size_t __bytes)
  3.     { return (((__bytes) + (size_t) _ALIGN-1) & ~((size_t) _ALIGN - 1)); }
很巧妙的办法,先将__bytes变成一个最接近的8的倍数还大一点,但不会大于7的数,再将低位的二进制1变为0,就是最接近的8的倍数了。看看_S_refill是怎么做的:由于它最优调用了_S_chunk_alloc,先看这个函数:

  1. /* We allocate memory in large chunks in order to avoid fragmenting */
  2. /* the malloc heap too much. */
  3. /* We assume that size is properly aligned. */
  4. /* We hold the allocation lock. */
  5. template <bool __threads, int __inst>
  6. char*
  7. __default_alloc_template<__threads, __inst>::_S_chunk_alloc(size_t __size,
  8.                                                             int& __nobjs)
  9. {
  10.     char* __result;
  11.     size_t __total_bytes = __size * __nobjs;
  12.     size_t __bytes_left = _S_end_free - _S_start_free;

  13.     if (__bytes_left >= __total_bytes) {
  14.         __result = _S_start_free;
  15.         _S_start_free += __total_bytes;
  16.         return(__result);
  17.     } else if (__bytes_left >= __size) {
  18.         __nobjs = (int)(__bytes_left/__size);
  19.         __total_bytes = __size * __nobjs;
  20.         __result = _S_start_free;
  21.         _S_start_free += __total_bytes;
  22.         return(__result);
  23.     } else {
  24.         size_t __bytes_to_get =  2 * __total_bytes + _S_round_up(_S_heap_size >> 4);
  25.         // Try to make use of the left-over piece.
  26.         if (__bytes_left > 0) {
  27.             _Obj* __STL_VOLATILE* __my_free_list = _S_free_list + _S_freelist_index(__bytes_left);

  28.             ((_Obj*)_S_start_free) -> _M_free_list_link = *__my_free_list;
  29.             *__my_free_list = (_Obj*)_S_start_free;
  30.         }
  31.         _S_start_free = (char*)malloc(__bytes_to_get);
  32.         if (0 == _S_start_free) {
  33.             size_t __i;
  34.             _Obj* __STL_VOLATILE* __my_free_list;
  35.      _Obj* __p;
  36.             // Try to make do with what we have. That can't
  37.             // hurt. We do not try smaller requests, since that tends
  38.             // to result in disaster on multi-process machines.
  39.             for (__i = __size;
  40.                  __i <= (size_t) _MAX_BYTES;
  41.                  __i += (size_t) _ALIGN) {
  42.                 __my_free_list = _S_free_list + _S_freelist_index(__i);
  43.                 __p = *__my_free_list;
  44.                 if (0 != __p) {
  45.                     *__my_free_list = __p -> _M_free_list_link;
  46.                     _S_start_free = (char*)__p;
  47.                     _S_end_free = _S_start_free + __i;
  48.                     return(_S_chunk_alloc(__size, __nobjs));
  49.                     // Any leftover piece will eventually make it to the
  50.                     // right free list.
  51.                 }
  52.             }
  53.      _S_end_free = 0;    // In case of exception.
  54.             _S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get);
  55.             // This should either throw an
  56.             // exception or remedy the situation. Thus we assume it
  57.             // succeeded.
  58.         }
  59.         _S_heap_size += __bytes_to_get;
  60.         _S_end_free = _S_start_free + __bytes_to_get;
  61.         return(_S_chunk_alloc(__size, __nobjs));
  62.     }
  63. }
前两个if还是很好理解的,一个是尝试分配__nobjs个__size个,能分配出去就分配,如果不足就先看最少能分配多少个__size分配出去。因为__nobjs是个值-结果参数,所以我们能知道最后分配了多少个__size.如果不足__size,尝试申请__bytes_to_get byte的内存。先将剩下的不足__size的那些内存挂到相应大小的链表里(肯定<128 bytes),然后尝试申请内存,如果申请失败就在大于等于__size的那些链表里面找看看是不是有,如果有,那么这一块就拿出来用。如果这也没有,再尝试用 

  1. _S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get);
分配内存,由上一节我们知道如果它失败,则会退出程序或抛出异常。如果成功,则再分配。

  1. template <bool __threads, int __inst>
  2. void*
  3. __default_alloc_template<__threads, __inst>::_S_refill(size_t __n)
  4. {
  5.     int __nobjs = 20;
  6.     char* __chunk = _S_chunk_alloc(__n, __nobjs);
  7.     _Obj* __STL_VOLATILE* __my_free_list;
  8.     _Obj* __result;
  9.     _Obj* __current_obj;
  10.     _Obj* __next_obj;
  11.     int __i;

  12.     if (1 == __nobjs) return(__chunk);
  13.     __my_free_list = _S_free_list + _S_freelist_index(__n);

  14.     /* Build free list in chunk */
  15.       __result = (_Obj*)__chunk;
  16.       *__my_free_list = __next_obj = (_Obj*)(__chunk + __n);
  17.       for (__i = 1; ; __i++) {
  18.         __current_obj = __next_obj;
  19.         __next_obj = (_Obj*)((char*)__next_obj + __n);
  20.         if (__nobjs - 1 == __i) {
  21.             __current_obj -> _M_free_list_link = 0;
  22.             break;
  23.         } else {
  24.             __current_obj -> _M_free_list_link = __next_obj;
  25.         }
  26.       }
  27.     return(__result);
  28. }
好了,如果上一次函数没有抛出异常,那么就谢天谢地了,终于有内存了,根据nobjs的值我们可以知道分配了多少倍我们需要的大小,如果为1,那么就没办剩余了,全得用掉,否则我们把多余的内存切成一块块大小为__n的块,挂到相应的链表中,以便下次需要。看看23行
  1. __current_obj -> _M_free_list_link = 0;
因为它知道上一次连表为空的情况下才调用的函数,所以它直接把最后一块指针置为NULL。

  1. /* __p may not be 0 */
  2.   static void deallocate(void* __p, size_t __n)
  3.   {
  4.     if (__n > (size_t) _MAX_BYTES)
  5.       malloc_alloc::deallocate(__p, __n);
  6.     else {
  7.       _Obj* __STL_VOLATILE* __my_free_list
  8.           = _S_free_list + _S_freelist_index(__n);
  9.       _Obj* __q = (_Obj*)__p;

  10.       // acquire lock
  11. # ifndef _NOTHREADS
  12.       /*REFERENCED*/
  13.       _Lock __lock_instance;
  14. # endif /* _NOTHREADS */
  15.       __q -> _M_free_list_link = *__my_free_list;
  16.       *__my_free_list = __q;
  17.       // lock is released here
  18.     }
  19.   }
释放内存就比较简单了,如果是小块的,那么直接放到相应链表中,如果是>128bytes时,用free释放掉。有了allocate的基础,reallocate也就比较简单了:

  1. template <bool threads, int inst>
  2. void*
  3. __default_alloc_template<threads, inst>::reallocate(void* __p,
  4.                                                     size_t __old_sz,
  5.                                                     size_t __new_sz)
  6. {
  7.     void* __result;
  8.     size_t __copy_sz;

  9.     if (__old_sz > (size_t) _MAX_BYTES && __new_sz > (size_t) _MAX_BYTES) {
  10.         return(realloc(__p, __new_sz));
  11.     }
  12.     if (_S_round_up(__old_sz) == _S_round_up(__new_sz)) return(__p);
  13.     __result = allocate(__new_sz);
  14.     __copy_sz = __new_sz > __old_sz? __old_sz : __new_sz;
  15.     memcpy(__result, __p, __copy_sz);
  16.     deallocate(__p, __old_sz);
  17.     return(__result);
  18. }
先判断用哪个类分配,再看看如果需要的内存是一样的,那么就不用变了,否则分配新内存。再根据分配的大小来决定拷贝多少数据放到新内存中,并deallocate掉老的内存。





阅读(2429) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~