STL源码剖析(1):空间配置器

  STL所有的操作对象(所有的数值)都存放在容器之内,容器需要分配空间以存放数据。为什么不说allocator是内存配置器而是空间配置器,因为空间不仅是内存,空间也可以是磁盘或其它辅助储存媒体。这里我们主要讨论内存配置。
   SGI STL每个容器缺省的空间配置器为alloc,如vector:

    template<class T, class Alloc = alloc> 
    class vector{……}

  一般而言,C++的内存配置和释放操作如下:

    class Object{……};
    Object* ob = new Object();
    delete ob;

  这里new其实包含两个操作,之前文章也曾提到:
  1) 分配空间
  2) 调用构造hanshu
  delete也包括两个操作:
  1) 调用析构函数
  2) 释放空间

  我们先来看构造和析构的代码
  STL allocator也有对应的两个操作,内存分配由alloc::allocate()负责,释放由alloc::deallocate()负责,构造由::construct()负责,析构由::destroy()负责。

template <class T>
inline void destroy(T* pointer) {
    pointer->~T();
}

template <class T1, class T2>
inline void construct(T1* p, const T2& value) {
  new (p) T1(value);
}

template <class ForwardIterator>
inline void
__destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) {
  for ( ; first < last; ++first)
    destroy(&*first);
}

template <class ForwardIterator> 
inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) {}

template <class ForwardIterator, class T>
inline void __destroy(ForwardIterator first, ForwardIterator last, T*) {
  typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;
  __destroy_aux(first, last, trivial_destructor());
}

template <class ForwardIterator>
inline void destroy(ForwardIterator first, ForwardIterator last) {
  __destroy(first, last, value_type(first));
}

  这里的new (p) T1(value)其实是个placement new,在已有空间调用构造函数。
  destroy()有两个版本,第一个版本接受一个对象指针,对单个对象进行析构。
  destroy()的第二版本,两个迭代器区间内所有对象进行析构。  
  这里的value_type()也是一个函数模版,其主要作用是,利用编译器的类型推导,得到迭代器的类型,再利用迭代器的属性萃取类型,得到迭代器的内嵌类型value_type。并利用编译器的类型推导,在__destroy()函数中取得迭代器的value_type内嵌类型。
  来看代码:

template <class Iterator>
inline typename iterator_traits<Iterator>::value_type*
value_type(const Iterator&) {
  return static_cast<typename iterator_traits<Iterator>::value_type*>(0);
}

  而对于__type_traits::has_trivial_destructor trivial_destructor,不同的数据类型会返回不同的类型:
  比如int是这样的:

__STL_TEMPLATE_NULL struct __type_traits<int> {
   typedef __true_type    has_trivial_default_constructor;
   typedef __true_type    has_trivial_copy_constructor;
   typedef __true_type    has_trivial_assignment_operator;
   typedef __true_type    has_trivial_destructor;
   typedef __true_type    is_POD_type;
};

  通过这个类型可以知道该析构函数是不是无关紧要的,若是,就不作处理;若不是,就遍历该区间,一一调用析构函数。

  在SGI STL中配置分为两级,第一级配置器和第二级配置器。
  第一级配置器直接使用malloc()和free(),第二级配置器视情况不同采取不同的策略。对于配置区大于128byte的,视为足够大,直接调用第一级配置器,对于小于128byte的内存分配请求,我们使用第二级内存配置器。第二级内存配置器是一个内存池,其中共有16个已分配好的区块形成的链表。这16个链表的中区块的大小依次是8,16,24….128byte,都8的倍数。每次请求小于等于128byte时,把请求的大小上调到最接近的8的倍数,比如,7就上调为8,30就上调为32,然后找到对应大小区块的链表,从中取下一个区块返回给请求。
  第二级配置器使用内存池的好处就是,可以避免太多小额区块造成的内存破碎。同时,每次分配内存都需要调用malloc去分配,malloc调用的消耗的时间等资源是一定的,对于大区块的分配这样的消耗的时间等资源,是没有什么的。但是对于小额区块的,它的消耗就显得太不值的了。我们可以采用一次预分配一块连续的大区块,把它串成一个定额大小的区块链表,(8的倍数字节),下次使用的时候,从对应预分配的区块链表中找一个能够满足大小的,最接近的区块直接返回给请求,这样就可以避免对于小区块的malloc调用。同时对于小区块的释放,可以直接把它加入到内存池中对应大小的链表中即可。
  第一级配置器是这样的,很简单:

static void * allocate(size_t n)
{
    void *result = malloc(n);
    if (0 == result) result = oom_malloc(n);
    return result;
}

  先调用malloc(),不成功调用oom_malloc():

template <int inst>
void * __malloc_alloc_template<inst>::oom_malloc(size_t n)
{
    void (* my_malloc_handler)();
    void *result;

    for (;;) {
        my_malloc_handler = __malloc_alloc_oom_handler;
        if (0 == my_malloc_handler) { __THROW_BAD_ALLOC; }
        (*my_malloc_handler)();
        result = malloc(n);
        if (result) return(result);
    }
}

  在for循环里先调用my_malloc_handler(),这个是用set_malloc_handler()设置(这是什么,戳这里:set_new_handler()总结),若为0,抛出_BAD_ALLOC,否则接着分配直到成功,恩,就是这样的。
  我们来看第二级配置器:
  在第二级配置器中维持一个free_list[16]数组,其中存储着8,16,24,32……128byte各种大小区块的链表的首节点地址。Free_list节点结构如下:

  union obj {
        union obj * free_list_link;
        char client_data[1];    /* The client sees this.        */
  };

  你没看错,是union,为了节省空间,不会维护链表指针造成内存的浪费,好方法。

  不啰嗦了,直接来看代码吧:

  /* n must be > 0      */
  static void * allocate(size_t n)
  {
    obj * __VOLATILE * my_free_list;
    obj * __RESTRICT result;

    if (n > (size_t) __MAX_BYTES) { //大于128byte,直接malloc()
        return(malloc_alloc::allocate(n));
    }
    my_free_list = free_list + FREELIST_INDEX(n); // //找到所属链表
    // 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
    result = *my_free_list;
    if (result == 0) {  //无空闲空间
        void *r = refill(ROUND_UP(n)); //请求分配
        return r;
    }
    *my_free_list = result -> free_list_link; //指向下一个空闲空间地址
    return (result);
  };

  /* p may not be 0 */
  static void deallocate(void *p, size_t n)
  {
    obj *q = (obj *)p;
    obj * __VOLATILE * my_free_list;

    if (n > (size_t) __MAX_BYTES) { //大于128byte,直接free()
        malloc_alloc::deallocate(p, n);
        return;
    }
    my_free_list = free_list + FREELIST_INDEX(n); //找到所属链表
    // acquire lock
#       ifndef _NOTHREADS
        /*REFERENCED*/
        lock lock_instance;
#       endif /* _NOTHREADS */
    q -> free_list_link = *my_free_list; //把该空间插入到链表头
    *my_free_list = q;
    // lock is released here
  }

/* We allocate memory in large chunks in order to avoid fragmenting     */
/* the malloc heap too much.                                            */
/* We assume that size is properly aligned.                             */
/* We hold the allocation lock.                                         */
template <bool threads, int inst>
char*
__default_alloc_template<threads, inst>::chunk_alloc(size_t size, int& nobjs)
{
    char * result;
    size_t total_bytes = size * nobjs; //所需空间
    size_t bytes_left = end_free - start_free; //内存池所剩空间

    if (bytes_left >= total_bytes) { //所剩空间足够
        result = start_free;
        start_free += total_bytes;
        return(result);
    } else if (bytes_left >= size) //所剩空间足够一个以上,小于20个
        nobjs = bytes_left/size;
        total_bytes = size * nobjs;
        result = start_free;
        start_free += total_bytes;
        return(result);
    } else { //一个都不够
        size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
        // Try to make use of the left-over piece.
        if (bytes_left > 0) { //把剩下的零头分配给合适的free链表
            obj * __VOLATILE * my_free_list =
                        free_list + FREELIST_INDEX(bytes_left);

            ((obj *)start_free) -> free_list_link = *my_free_list;
            *my_free_list = (obj *)start_free;
        }
        start_free = (char *)malloc(bytes_to_get); //向heap申请空间
        if (0 == start_free) { //啊,又失败了
            int i;
            obj * __VOLATILE * my_free_list, *p;
            // Try to make do with what we have.  That can't
            // hurt.  We do not try smaller requests, since that tends
            // to result in disaster on multi-process machines.
            for (i = size; i <= __MAX_BYTES; i += __ALIGN) { //向其他兄弟free list借
                my_free_list = free_list + FREELIST_INDEX(i);
                p = *my_free_list;
                if (0 != p) {
                    *my_free_list = p -> free_list_link;
                    start_free = (char *)p;
                    end_free = start_free + i;
                    return(chunk_alloc(size, nobjs)); //递归调用,这么辛苦不要浪费                    
                    // Any leftover piece will eventually make it to the right free list.
                }
            }
            end_free = 0;   // In case of exception. 哦买噶,还不行,只能调用第一级配置器了
            start_free = (char *)malloc_alloc::allocate(bytes_to_get);
            // This should either throw an
            // exception or remedy the situation.  Thus we assume it
            // succeeded.
        }
        heap_size += bytes_to_get;
        end_free = start_free + bytes_to_get;
        return(chunk_alloc(size, nobjs));
    }
}


/* Returns an object of size n, and optionally adds to size n free list.*/
/* We assume that n is properly aligned.                                */
/* We hold the allocation lock.                                         */
template <bool threads, int inst>
void* __default_alloc_template<threads, inst>::refill(size_t n)
{
    int nobjs = 20;
    char * chunk = chunk_alloc(n, nobjs);  //默认分配20个n字节的空间
    obj * __VOLATILE * my_free_list;
    obj * result;
    obj * current_obj, * next_obj;
    int i;

    if (1 == nobjs) return(chunk);
    my_free_list = free_list + FREELIST_INDEX(n);

    /* Build free list in chunk */
      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 -> free_list_link = 0;
            break;
        } else {
            current_obj -> free_list_link = next_obj;
        }
      }
    return(result);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

One2zeror

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值