allocator(分配器)

allocator是容器的空间分配器,负责对容器进行空间申请和释放。

allocator的必要接口

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

allocator::allocator()
allocator::allocator(const allocator&)
template<class U> allocator::allocator(const allocator<U>&)
allocator::~allocator()
pointer allocator::address(reference x) const
const_pointer allocator::address(const_reference x) const
pointer allocator::allocate(size_type n, const void* = 0)
void allocator::deallocate(pointer p, size_type n)
size_type allocator::max_size() const
void allocator::construct(pointer p, const T& x)
void allocator::destory(pointer p)

设计一个简单的空间配置器

template<class T>
inline T* _allocate(ptrdiff_t size, T*){
    set_new_handler(0);
    T* tmp = (T*)(::operator new((size_t)(size * sizeof(T))));
    if(tmp == 0){
        cerr << "out of memory" << endl;
        exit(1);
    }
    return tmp;
}

template<class T>
inline void _deallocate(T* buffer){
    ::operator delete(buffer);
}

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

template<class T>
inline void _destory(T* ptr){
    ptr->~T();
}

template<class T>
class allocator {
public:
    typedef T value_type;
    typedef T* pointer;
    typedef const T* const_pointer;
    typedef T& reference;
    typedef const T& const_reference;
    typedef size_t size_type;
    typedef ptrdiff_t difference_type   

    template<class U>
    struct rebind{
        typedef allocator<U> other;
    };

    pointer allocate(size_type n, const void* hint=0){
        return _allocate((difference_type)n, (pointer)0);
    }

    void deallocate(pointer p, size_type n) { _deallocate(p); }

    void construct(pointer p, const T& value){
        _construct(p, value);
    }

    void destory(pointer p) { _destory(p); }

    pointer address(reference x) { return (pointer)&x; }

    const_pointer const_address(const_reference x) { return (const_pointer)&x; }

    size_type max_size() const {
        return size_type(UINT_MAX/sizeof(T));
    }
}    

SGI空间配置器

SGI有标准的空间配置器,但是不用。因为效率不佳。SGI只使用自己特殊的空间配置器。

stl将allocator::allocate负责内存配置,allocator::construct负责对象构建;allocator::destory负责对象析构,allocator::deallocate负责内存释放。

memory —–> <stl_construct.h> 包含全局函数construct和destory
memory —–> <stl_alloc.h> 包含allocate和deallocate
memory —–> <stl_uninitialized.h> 用来进行填充或者复制数据到内存中

合理高效的为容器元素分配空间并初始化数据是allocate的重要作用。

construct和destory的实现

#include<new.h>          //使用placement new,需要包含该文件。placement new是重载operator new的一个标准、全局的版本,它不能被自定义的版本代替。作用:创建对象(调用该类的构造函数)但是不分配内存,而是在已有的内存块上面创建对象。用于需要反复创建并删除的对象上,可以降低分配释放内存的性能消耗。

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

template<class T>
inline void destory(T* pointer){
    pointer->~T();            //调用元素的析构函数
}

template<class ForwardIterator>
inline void destory(ForwardIterator first, ForwardIterator last){
    __destory(first, last, value_type(first));   //判断元素类型,以便进行高效操作
}

template<class ForwardIterator, class T>
inline void __destory(ForwardIterator first, ForwardIterator last, T*){
    typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;   //萃取出该类型的析构函数是否为trivial(可以理解为成员变量是否开辟了堆空间)
    __destroy_aux(first, last, trivial_destructor());
}

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

template<class ForwardIterator>
inline void __destory_aux(ForwardIterator first, ForwardIterator last, __true_type) {}


SGI中的std::alloc

SGI设计了双层级配置器,第一级配置器直接使用malloc和free,第二级配置器则视情况采用不同的策略:当配置区块超过128bytes时,视为“足够大”,调用第一级配置器;当配置区块小于128byte时,为了降低额外负担(申请一块内存时,需要额外内存cookie来记录内存的大小),便采用memory pool整理方式,使用第二级配置器。

并且,SGI为自己的配置器封装了接口,使能够符合STL规格:

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 void deallocate(T* p, size_t n){
          if(0 != n){
              Alloc::deallocate(p, n * sizeof(T));
          }
      }
}

第一级配置器 __malloc_alloc_template剖析

#if 0
#include<new>
#define __THROW_BAD_ALLOC throw bad_alloc
#elif !defined(__THROW_BAD_ALLOC)
#include<iostream.h>
#define __THROW_BAD_ALLOC cerr<< "out of memory" <<endl; exit(1)
#endif

template<int inst>   //这里的非类型参数没有用处
class __malloc_alloc_template{
  private:            //用来处理内存不足的情况
    static void *oom_malloc(size_t);
    static void *oom_realloc(void*, size_t);
    static void (* __malloc_alloc_oom_handler)();        //static void* __malloc_alloc_oom_handler();

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

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

    static void* reallocate(void* p, size_t /* old_sz */, size_t new_sz){
        void* result = realloc(p, new_sz);    //直接使用realloc,先判断当前的指针是否有足够的连续空间,如果有,扩大mem_address指向的地址,并且将mem_address返回,如果空间不够,先按照newsize指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来mem_address所指内存区域(注意:原来指针是自动释放,不需要使用free),同时返回新分配的内存区域的首地址。即重新分配存储器块的地址
        if(0 == result){
            result = oom_realloc(p, new_sz);
        }
        return result;
    }

    static void (* set_malloc_handler(void (*f)()))() {           //接受客户自定义的handler,返回值为函数指针
        void (*old)() = __malloc_alloc_oom_handler;
        __malloc_alloc_oom_handler = f;
        return(old);
    }

    template<int inst>
    static void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0;

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

        for(;;){        //不断尝试,直至分配成功
            my_malloc_handler = __malloc_alloc_oom_handler;
            if(0 == my_alloc_handler) {       //若客户没有定义的handler,那么便给系统抛出错误
                __THROW_BAD_ALLOC;
            }
            (*my_malloc_handler)();    //客户定义了handler,便运行
            result = malloc(n);        //handler处理之后,尝试再次分配内存
            if(result){
                return result;
            }
        }
    }

    template<int inst>
    static void* __malloc_alloc_template<inst>::oom_realloc(void*, size_t){
        void (*my_malloc_handler)();
        void *result;


        for(;;){
            my_malloc_handler = __malloc_alloc_oom_handler;
            if(0 == my_alloc_handler) {       //若客户没有定义的handler,那么便给系统抛出错误
                __THROW_BAD_ALLOC;
            }
            (*my_malloc_handler)();    //客户定义了handler,便运行
            result = realloc(n);        //handler处理之后,尝试再次分配内存
            if(result){
                return result;
            }
        }
    }
};

第二级配置器 __default_alloc_template 剖析

避免太多小区块造成内存的碎片,但是更重要的是每次为元素申请内存时,都会有cookie记录内存大小。如果多次申请小区块,那么就会导致cookie的消耗极大。

为了方便管理,SGI配置器会主动将任何小额区块的内存需求量上调至8的倍数,因为配置器维护了16个free-list,各自管理大小8,….128byte的小额区块。free-list的节点结构如下:

union obj {
  union obj* free_list_link;
  char cleint_data[1];    //使用union减少内存的使用
};

配置器的实现

enum {
    __ALIGN = 8;     //区块的边界和最小尺寸
}; 
enum{
  __MAX_BYTES = 128;   //区块的最大尺寸
};
enum{
  __NFREELISTS = __MAX_BYTES/__ALIGN;       //free-list个数  
};

template<bool threads, int inst> //第一参数用于多线程环境下,第二参数不用
class __default_alloc_template{
    private:
      static size_t ROUND_UP(size_t bytes){  //将元素的大小bytes上调为8的倍数
          return (((bytes) + __ALIGN - 1) & ~(__ALIGN - 1));
      }
    private:
      union obj {
          union obj* free_list_link;
          char cleint_data[1];   
      };
    private:
      static obj* volatile free_list[__NFRESSLISTS];      //创建__NFRESSLISTS个free_list
      static size_t FREELIST_INDEX(size_t bytes){    //根据元素类型的大小确定在那个list中
          return  (((bytes) + __ALIGN - 1) / __ALIGN - 1);
      }

      static void* refill(size_t n) {   //申请内存的中介
          int nobjs = 20;
          char *chunk = chunk_alloc(n, nobjs);   //找到内存池给该free_list分配空间
          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);    //有多个区块申请成功,加到free_list上

          result = (obj*)chunk;

      }
      static char* chunk_alloc(size_t size, int &nobjs); //配置一大块空间,可容纳nobjs个大小为size的区块

      static char* start_free;        //内存池起始位置
      static char* end_free;          //内存池结束位置
      static size_t heap_size;        //内存池的大小

    public:
      static void* allocate(size_t n){
          obj* volatile * my_free_list;
          obj* result;
          if(n > (size_t) __MAX_BYTES){
              return (malloc_alloc::allocate(n));
          }

          my_free_list = free_list + FREELIST_INDEX(n);  //定位到该尺寸的free_list,拿到“头指针”
          result = *my_free_list;
          if(result == 0){       //free_list还未初始化或者已经没有多余的内存
              void* r = refill(ROUND_UP(N));  //找refill申请内存
              return r;
          }
          *my_free_list = result->free_list_link;
          return result;
      }
      static void deallocate(void* p, size_t n){
          obj* q = (obj*)p;
          obj* volatile * my_free_list;
          obj* result;
          if(n > (size_t) __MAX_BYTES){
              malloc_alloc::deallocate(p, n);
              return;
          }
          my_free_list = free_list + FREELIST_INDEX(n);  //定位到该尺寸的free_list,拿到“头指针”
          q->free_list_link = *my_free_list;
          *my_free_list = q;

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

//成员变量的初值设定
template<bool threads, int inst>
char* __default_alloc_template<threads, inst>::start_free = 0;

template<bool threads, int inst>
char* __default_alloc_template<threads, inst>::end_free = 0;

template<bool threads, int inst>
size_t __default_alloc_template<threads, inst>::heap_size = 0;

template<bool threads, int inst>
__default_alloc_template<threads, inst>::obj* volatile __default_alloc_template<threads, inst>::free_list[__NFREELISTS] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

chunk_alloc

template<bool threads, int inst>
char* __default_alloc_template<threads, inst>::chunk_alloc(size_t size, int& nobjs){  //nobjs是引用类型,可以告诉refill分配了多少个区块
    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);  //直接申请2倍客户端需要的内存
        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){  //在原有的free_lists上找到比申请free_list区块大的free_list,如果这些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);
                }
            }
            end_free = 0;
            start_free =  (char*)malloc_alloc::allocate(bytes_to_get);  //别的大区块链表中也没有内存,那么只能调用第一级配置器,第一级配置器里面有客户自定义的内存申请失败的处理函数,函数运行后,可能会申请成功
        }
        heap_size += bytes_to_get;
        end_free = start_free + bytes_to_get;
        return chunk_alloc(size, nobjs);
    }
}
  • free_lists是一个包含了16个free_list链表头指针的数组。
  • 每一个free_list是一个有自己区块尺寸规格的链表,并且每个链表是使用头插法进行区块增加的。取出区块也是在头部。不包含没有数据的头节点,头指针直接指向第一个区块。
  • free_lists初始化都为空,也就是说每个free_list都是空。

内存基本处理工具

  • construct
  • destory
  • uninitialized_copy
  • uninitialized_fill
  • uninitialized_fill_n

后面三个函数可以使内存配置和对象的构造行为分离开。它们会调用construct来copy对象到相应迭代器所在的内存空间中。

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top