码迷,mamicode.com
首页 > 其他好文 > 详细

STL 源码剖析读书笔记一:空间配置器

时间:2016-05-06 16:08:45      阅读:223      评论:0      收藏:0      [点我收藏+]

标签:

1. STL 的空间配置器

STL 空间配置器在运用的角度来说,是最不需要介绍的,它总是隐藏在一切组件背后。但若以 STL 的实现角度而言,第一个需要理解的就是空间配置器。

根据 STL 规范,以下是 allocator 的必要接口:

allocator::value_type
allocator::pointer
allocator::const_pointer
allocator::reference
allocator::const_reference
allocator::size_type
allocator::difference_type

//一个嵌套的class template.class rebind<U>拥有唯一成员other,那allocator::allocator()
allocator::rebind 

//default constructor
allocator::allocator() 

//copy constructor
allocator::allocator(const allocator &) 

//泛化的copy constructor
template<class U>
allocator::allocator(const allocator<U> &) 

//destructor
allocator::~allocator() 

//返回某个对象的地址。算式a.address(x)等同于&x
pointer allocator::address(reference x) const 

//返回某个const对象的地址。算式a.address(x)等同于&x
const_pointer allocator::address(const_referenc x) const 

//配置空间,足以存储n个T对象。第二参数是个提示。实现上可能会利用它来增进区域性,或完全忽略之。
pointer allocator::allocate(size_type n ,const void * = 0 ) 

//归还先前配置的空间
void allocator::deallocate(pointer p ,size_type n)

//返回可成功配置的最大量
size_type allocator::max_size() const

//等同于new((void *)p) T(x)
void allocator::construct(pointer p ,const T& x) 

//等同于p->~T()
void allocator::destory(pointer p)</span>

2. 具备次配置力的 SGI 空间配置器

SGI 的配置器与众不同,也与标准规范不同,其名称是 alloc,而不是 allocator,而且不接受任何参数。SGI 也有定义一个符合部分标准、名为 allocator 的配置器,但 SGI 自己从未用过它,也不建议我们使用,主要原因是效率不佳,只是对 ::operator new 和 ::operator delete 的简单包装。

为了精密分工,STL allocator 决定将 内存配置/释放 和对象构造/析构 两个阶段的操作区分开来,分别由如下函数来实现:

alloc::allocator()          //内存配置
alloc::deallocator()        //内存配置

::construcct()              //对象构造
::destroy()                 //对象析构

技术分享


2.1 构造和析构基本工具:construct() 和 destroy()

以下是construct() 和 destroy()的实现:


#ifndef __SGI_STL_INTERNAL_CONSTRUCT_H
#define __SGI_STL_INTERNAL_CONSTRUCT_H

#include <new.h>

__STL_BEGIN_NAMESPACE

// 使用 placement new 构造对象,调用构造函数 T1::T1(value);
template <class T1, class T2>
inline void construct(T1* p, const T2& value) {
  new (p) T1(value);
}

// 接受一个指针的 destroy 函数
template <class T>
inline void destroy(T* pointer) {
    pointer->~T();
}

// 接受两个迭代器,使用 value_type() 获得迭代器所指对象的数值型别
template <class ForwardIterator>
inline void destroy(ForwardIterator first, ForwardIterator last) {
  __destroy(first, last, value_type(first));
}

// 使用 __type_traits<> 判断该类型析构函数是否为 trivial
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());
}

// 对象析构函数为 trivial,什么都不用做
template <class ForwardIterator> 
inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) {}

// 对象析构函数为 non-trivial,这时需要对迭代器范围内的每一个元素分别调用 destroy()
template <class ForwardIterator>
inline void
__destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) {
  for ( ; first < last; ++first)
    destroy(&*first);
}

// destroy() 针对 char* 和 wchar* 的特化版
inline void destroy(char*, char*) {}
inline void destroy(wchar_t*, wchar_t*) {}

__STL_END_NAMESPACE

#endif /* __SGI_STL_INTERNAL_CONSTRUCT_H */

2.2 空间的配置与释放

对象构造前的空间配置和对象析构后的空间释放由 stl_alloc.h 负责,SGI 对此的设计哲学如下:

  • 向 system heap 要求空间
  • 考虑多线程状态
  • 考虑内存不足时的应变措施
  • 考虑过多“小型区块”可能造成的内存碎片(fragment)问题

alloc 配置内存和释放内存使用的是 malloc() 和 free()。
考虑到小型区块可能造成内存碎片问题,SGI 设计了双层配置器,第一级配置器直接使用 malloc 和 free,第二级配置器视情况不同采用不同的配置器:

  • 当配置区块超过128 byte 时,视为足够大,调用第一级配置器
  • 当配置区块小于128 byte 时,视为过小,采用内存池的方式配置内存

只开放第一级配置器还是同时开发第二级配置器取决于 __USE_MALLOC 是否被定义:

无论 alloc 被定义为哪一级配置器,SGI 再为它包装一个接口如下:

template<class T, class Alloc>
class simple_alloc {

public:
    static T *allocate(size_t n)
                { return 0 == n? 0 : (T*) Alloc::allocate(n * sizeof (T)); }
    static T *allocate(void)
                { return (T*) Alloc::allocate(sizeof (T)); }
    static void deallocate(T *p, size_t n)
                { if (0 != n) Alloc::deallocate(p, n * sizeof (T)); }
    static void deallocate(T *p)
                { Alloc::deallocate(p, sizeof (T)); }
};

两级配置器的关系:

技术分享

SGI STL 容器全部使用这个 simple_alloc 接口,例如:

template <class T, class Alloc = alloc>
class vector {
public:
  typedef T value_type;
  typedef value_type* pointer;
  ...
  typedef simple_alloc<value_type, Alloc> data_allocator;
  iterator start;
  iterator finish;
  iterator end_of_storage;
  ...

  void deallocate() {
    if (start) data_allocator::deallocate(start, end_of_storage - start);
  }

2.3 第一级配置器 __malloc_alloc_template


template <int inst>
class __malloc_alloc_template {

private:
// 用于处理内存不足的情况
static void *oom_malloc(size_t);
static void *oom_realloc(void *, size_t);
#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
    static void (* __malloc_alloc_oom_handler)();
#endif

public:

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

// 直接 free
static void deallocate(void *p, size_t /* n */)
{
    free(p);
}

// 直接 realloc
static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz)
{
    void * result = realloc(p, new_sz);
    if (0 == result) result = oom_realloc(p, new_sz);
    return result;
}

// 设置自己的 out-of-memory-handler
static void (* set_malloc_handler(void (*f)()))()
{
    void (* old)() = __malloc_alloc_oom_handler;
    __malloc_alloc_oom_handler = f;
    return(old);
}

};


// malloc_alloc out-of-memory handling

#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
template <int inst>
void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0;
#endif

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);
    }
}

template <int inst>
void * __malloc_alloc_template<inst>::oom_realloc(void *p, 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 = realloc(p, n);      // 再次尝试配置内存
        if (result) return(result);
    }
}

typedef __malloc_alloc_template<0> malloc_alloc;

第一级配置器的 allocate 和 realloc 都是在调用 malloc 和 realloc 不成功后,改调用 oom_malloc 和 oom_realloc,后两者都有内循环,不断调用内存不足处理例程,期望在某次调用后,获得足够内存而圆满完成任务。若内存不足处理例程未被设定,oom_malloc 和 oom_realloc 便调用 __THROW_BAD_ALLOC抛出 bad_alloc 异常或利用 exit(1) 中止程序。设计和设定内存不足处理例程都是客端的责任。


2.4 第二级配置器 __malloc_alloc_template

enum {__ALIGN = 8};
enum {__MAX_BYTES = 128};
enum {__NFREELISTS = __MAX_BYTES/__ALIGN};

template <bool threads, int inst>
class __default_alloc_template {
private:
  static size_t ROUND_UP(size_t bytes) {
        return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));
  }
private:
  union obj {
        union obj * free_list_link;
        char client_data[1];    /* The client sees this.        */
  };
private:
  static obj * __VOLATILE free_list[__NFREELISTS]; 
  static  size_t FREELIST_INDEX(size_t bytes) {
        return (((bytes) + __ALIGN-1)/__ALIGN - 1);
  }


  static void *refill(size_t n);
  static char *chunk_alloc(size_t size, int &nobjs);

  // 内存池
  static char *start_free;
  static char *end_free;
  static size_t heap_size;

public:

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

    if (n > (size_t) __MAX_BYTES) {
        return(malloc_alloc::allocate(n));
    }
    my_free_list = free_list + FREELIST_INDEX(n);
    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) {
        malloc_alloc::deallocate(p, n);
        return;
    }
    my_free_list = free_list + FREELIST_INDEX(n);
    q -> free_list_link = *my_free_list;
    *my_free_list = q;
  }

  static void * reallocate(void *p, size_t old_sz, size_t new_sz);

} ;

typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc;
typedef __default_alloc_template<false, 0> single_client_alloc;

// 注意:njobs是引用参数
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) {     // 内存池剩余空间不能完全满足需求量,但能提供一个(以上)的区块
        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_list
            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); // 申请堆内存,补充内存池
        if (0 == start_free) {
            int i;
            obj * __VOLATILE * my_free_list, *p;

            for (i = size; i <= __MAX_BYTES; i += __ALIGN) {
                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;
                    // 递归调用自己,修正 njobs 并返回
                    return(chunk_alloc(size, nobjs));
                    // 注意:任何残余零头终将被编入 free_list中备用
                }
            }
            end_free = 0;   // 出现意外,无内存可用
            start_free = (char *)malloc_alloc::allocate(bytes_to_get);
            // 抛出异常或者内存不足的情况获得改善
        }
        // 申请堆内存成功,调整内存池
        heap_size += bytes_to_get;
        end_free = start_free + bytes_to_get;
        // 递归调用自己,修正 njobs 并返回
        return(chunk_alloc(size, nobjs));
    }
}

template <bool threads, int inst>
void* __default_alloc_template<threads, inst>::refill(size_t n)
{
    int nobjs = 20;
    // 调用 chunk_alloc,尝试取得 njobs 个区块作为 free_list 的新节点
    char * chunk = chunk_alloc(n, nobjs);
    obj * __VOLATILE * my_free_list;
    obj * result;
    obj * current_obj, * next_obj;
    int i;

    // 只获得一个区块
    if (1 == nobjs) return(chunk);
    // 准备调整 free_list,加入新节点
    my_free_list = free_list + FREELIST_INDEX(n);

    // 在 chunk_alloc 中得到的空间构建 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 -> free_list_link = 0;
            break;
        } else {
            current_obj -> free_list_link = next_obj;
        }
      }
    return(result);
}

template <bool threads, int inst>
void*
__default_alloc_template<threads, inst>::reallocate(void *p,
                                                    size_t old_sz,
                                                    size_t new_sz)
{
    void * result;
    size_t copy_sz;
    // 如果分配区块大小 > 128bytes,直接 realloc
    if (old_sz > (size_t) __MAX_BYTES && new_sz > (size_t) __MAX_BYTES) {
        return(realloc(p, new_sz));
    }
    if (ROUND_UP(old_sz) == ROUND_UP(new_sz)) return(p);
    result = allocate(new_sz);
    copy_sz = new_sz > old_sz? old_sz : new_sz;
    memcpy(result, p, copy_sz);
    deallocate(p, old_sz);
    return(result);
}

上面是部分源码,删掉了多线程的那部分代码。为了方便管理,SGI 第二级配置器会主动将任何小额区块的内存需求量上调至 8 的倍数(例如客户端要求30bytes,就自动调整为 32 bytes),并维护16个 free-lists,各自管理大小分别为:8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128 bytes的小额区块。

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

对于 联合体 obj,可以理解为:

  • 当 obj 对象未被用户使用时,作为链表指针存在指向下一个obj,这个时候是不需要内存中的数据的
  • 当 obj 对象已被用户使用时,这块内存被分配给用户,client_data用来存数据(client_data 的大小并不是1,而是前面讲的 8 的 16 个整倍数),此时链表指针就没用了

也就是说,每个 obj 在一个时刻只有一个字段有实际意义(free_list_link 或者 client_data),其主要目的是节省内存。

3. 内存基本处理工具

STL 定义有 5 个全局函数,作用于未初始化空间上。前两个是 construct() 和 destroy(),另外 3 个是:

uninitialized_copy()
uninitialized_fill()
uninitialized_fill_n()

它们分别对应于高层次函数 copy()、 fill()、 fill_n(),这些都是 STL 的算法。

uninitialized_copy()、uninitialized_fill()、uninitialized_fill_n() 实际定于于 stl_uninitialized。

// Valid if copy construction is equivalent to assignment, and if the
//  destructor is trivial.
template <class InputIterator, class ForwardIterator>
inline ForwardIterator 
__uninitialized_copy_aux(InputIterator first, InputIterator last,
                         ForwardIterator result,
                         __true_type) {
  return copy(first, last, result);
}

template <class InputIterator, class ForwardIterator>
ForwardIterator 
__uninitialized_copy_aux(InputIterator first, InputIterator last,
                         ForwardIterator result,
                         __false_type) {
  ForwardIterator cur = result;
  __STL_TRY {
    for ( ; first != last; ++first, ++cur)
      construct(&*cur, *first);
    return cur;
  }
  __STL_UNWIND(destroy(result, cur));
}

template <class InputIterator, class ForwardIterator, class T>
inline ForwardIterator
__uninitialized_copy(InputIterator first, InputIterator last,
                     ForwardIterator result, T*) {
  typedef typename __type_traits<T>::is_POD_type is_POD;
  return __uninitialized_copy_aux(first, last, result, is_POD());
}

template <class InputIterator, class ForwardIterator>
inline ForwardIterator
  uninitialized_copy(InputIterator first, InputIterator last,
                     ForwardIterator result) {
  return __uninitialized_copy(first, last, result, value_type(result));
}

inline char* uninitialized_copy(const char* first, const char* last,
                                char* result) {
  memmove(result, first, last - first);
  return result + (last - first);
}

inline wchar_t* uninitialized_copy(const wchar_t* first, const wchar_t* last,
                                   wchar_t* result) {
  memmove(result, first, sizeof(wchar_t) * (last - first));
  return result + (last - first);
}

template <class ForwardIterator, class T>
inline void
__uninitialized_fill_aux(ForwardIterator first, ForwardIterator last, 
                         const T& x, __true_type)
{
  fill(first, last, x);
}

template <class ForwardIterator, class T>
void
__uninitialized_fill_aux(ForwardIterator first, ForwardIterator last, 
                         const T& x, __false_type)
{
  ForwardIterator cur = first;
  __STL_TRY {
    for ( ; cur != last; ++cur)
      construct(&*cur, x);
  }
  __STL_UNWIND(destroy(first, cur));
}

template <class ForwardIterator, class T, class T1>
inline void __uninitialized_fill(ForwardIterator first, ForwardIterator last, 
                                 const T& x, T1*) {
  typedef typename __type_traits<T1>::is_POD_type is_POD;
  __uninitialized_fill_aux(first, last, x, is_POD());

}

template <class ForwardIterator, class T>
inline void uninitialized_fill(ForwardIterator first, ForwardIterator last, 
                               const T& x) {
  __uninitialized_fill(first, last, x, value_type(first));
}

// Valid if copy construction is equivalent to assignment, and if the
//  destructor is trivial.
template <class ForwardIterator, class Size, class T>
inline ForwardIterator
__uninitialized_fill_n_aux(ForwardIterator first, Size n,
                           const T& x, __true_type) {
  return fill_n(first, n, x);
}

template <class ForwardIterator, class Size, class T>
ForwardIterator
__uninitialized_fill_n_aux(ForwardIterator first, Size n,
                           const T& x, __false_type) {
  ForwardIterator cur = first;
  __STL_TRY {
    for ( ; n > 0; --n, ++cur)
      construct(&*cur, x);
    return cur;
  }
  __STL_UNWIND(destroy(first, cur));
}

template <class ForwardIterator, class Size, class T, class T1>
inline ForwardIterator __uninitialized_fill_n(ForwardIterator first, Size n,
                                              const T& x, T1*) {
  typedef typename __type_traits<T1>::is_POD_type is_POD;
  return __uninitialized_fill_n_aux(first, n, x, is_POD());

}

template <class ForwardIterator, class Size, class T>
inline ForwardIterator uninitialized_fill_n(ForwardIterator first, Size n,
                                            const T& x) {
  return __uninitialized_fill_n(first, n, x, value_type(first));
}

// Copies [first1, last1) into [result, result + (last1 - first1)), and
//  copies [first2, last2) into
//  [result, result + (last1 - first1) + (last2 - first2)).

template <class InputIterator1, class InputIterator2, class ForwardIterator>
inline ForwardIterator
__uninitialized_copy_copy(InputIterator1 first1, InputIterator1 last1,
                          InputIterator2 first2, InputIterator2 last2,
                          ForwardIterator result) {
  ForwardIterator mid = uninitialized_copy(first1, last1, result);
  __STL_TRY {
    return uninitialized_copy(first2, last2, mid);
  }
  __STL_UNWIND(destroy(result, mid));
}

// Fills [result, mid) with x, and copies [first, last) into
//  [mid, mid + (last - first)).
template <class ForwardIterator, class T, class InputIterator>
inline ForwardIterator 
__uninitialized_fill_copy(ForwardIterator result, ForwardIterator mid,
                          const T& x,
                          InputIterator first, InputIterator last) {
  uninitialized_fill(result, mid, x);
  __STL_TRY {
    return uninitialized_copy(first, last, mid);
  }
  __STL_UNWIND(destroy(result, mid));
}

// Copies [first1, last1) into [first2, first2 + (last1 - first1)), and
//  fills [first2 + (last1 - first1), last2) with x.
template <class InputIterator, class ForwardIterator, class T>
inline void
__uninitialized_copy_fill(InputIterator first1, InputIterator last1,
                          ForwardIterator first2, ForwardIterator last2,
                          const T& x) {
  ForwardIterator mid2 = uninitialized_copy(first1, last1, first2);
  __STL_TRY {
    uninitialized_fill(mid2, last2, x);
  }
  __STL_UNWIND(destroy(first2, mid2));
}

STL 源码剖析读书笔记一:空间配置器

标签:

原文地址:http://blog.csdn.net/fool_duck/article/details/51313185

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!