STL源码剖析之空间配置器

1.空间分配器的标准接口

通常,C++内存分配和释放的操作如下:

class Foo {...};
Foo *pf = new Foo;
delete pf;
  • new内含2阶段操作:
    • 调用::operator new分配内存
    • 调用构造函数构造对象
  • delete也含2阶段操作:
    • 调用析构函数析构对象
    • 调用::operator delete释放内存

STL allocator将new和delete的2阶段操作进行了分离:

  • 内存分配:由alloc::allocate()负责
  • 内存释放:由alloc::deallocate()负责
  • 对象构造:由alloc::construct()负责
  • 对象析构:由alloc::destroy负责

2.SGI标准的空间分配器std::allocator

虽然SGI也定义有一个符合”部分“标准、名为allocator的分配器,但SGI自己从未用过它,也不建议我们使用。主要原因是效率不佳,只把C++的::operator new和::operator delete做一层薄薄的包装而已

std::allocator源代码

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;

public:
  static T*   allocate();
  static T*   allocate(size_type n);

  static void deallocate(T* ptr);
  static void deallocate(T* ptr, size_type n);

  static void construct(T* ptr);
  static void construct(T* ptr, const T& value);
  static void construct(T* ptr, T&& value);

  template <class... Args>
  static void construct(T* ptr, Args&& ...args);

  static void destroy(T* ptr);
  static void destroy(T* first, T* last);
};

template <class T>
T* allocator<T>::allocate()
{
  return static_cast<T*>(::operator new(sizeof(T)));
}

template <class T>
T* allocator<T>::allocate(size_type n)
{
  if (n == 0)
    return nullptr;
  return static_cast<T*>(::operator new(n * sizeof(T)));
}

template <class T>
void allocator<T>::deallocate(T* ptr)
{
  if (ptr == nullptr)
    return;
  ::operator delete(ptr);
}

template <class T>
void allocator<T>::deallocate(T* ptr, size_type /*size*/)
{
  if (ptr == nullptr)
    return;
  ::operator delete(ptr);
}

template <class T>
void allocator<T>::construct(T* ptr)
{
  mystl::construct(ptr);
}

template <class T>
void allocator<T>::construct(T* ptr, const T& value)
{
  mystl::construct(ptr, value);
}

template <class T>
 void allocator<T>::construct(T* ptr, T&& value)
{
  mystl::construct(ptr, mystl::move(value));
}

template <class T>
template <class ...Args>
 void allocator<T>::construct(T* ptr, Args&& ...args)
{
  mystl::construct(ptr, mystl::forward<Args>(args)...);
}

template <class T>
void allocator<T>::destroy(T* ptr)
{
  mystl::destroy(ptr);
}

template <class T>
void allocator<T>::destroy(T* first, T* last)
{
  mystl::destroy(first, last);
}

3.SGI特殊的空间分配器std::alloc
STL标准规定分配器定义于中,SGI内含两个文件,负责分离的2阶段操作
在这里插入图片描述

真正在SGI STL中大显身手的分配器(即SGI特殊的空间分配器std::alloc)或为第一级分配器,或为第二级分配器

3.1 对象构造与析构

<stl_construct.h>
在这里插入图片描述

STL规定分配器必须拥有名为construct()和destroy()的两个成员函数,然而SGI特殊的空间分配器std::alloc并未遵守这一规则,所以实际上这部分属于STL allocator,但不属于std::alloc。换句话说,SGI特殊的空间分配器std::alloc不包含”3.1 对象构造与析构“,只包含”3.2 内存分配与释放“

construct.h源代码

    template<class T1, class T2>
    inline void construct(T1 *ptr1, const T2& value){
        new(ptr1) T1(value); //palcement new 调用T1::T1(value)
    }

    //destroy()的第一个版本,接受一个指针
    template<class T>
    inline void destroy(T *ptr){
        ptr->~T();
    }
    
    //destroy()的第二个版本,接受两个迭代器,此函数设法找出元素的数值型别
    //进而利用__type_traits<>求取最适当的措施
    template<class ForwardIterator>
    inline void _destroy(ForwardIterator first, ForwardIterator last, _true_type){}

    //如果元素的数值型别有 non-trival condestructor
    template<class ForwardIterator>
    inline void _destroy(ForwardIterator first, ForwardIterator last, _false_type){
        for (; first != last; ++first){
            destroy(&*first);
        }
    }

    //判断元素的数值型憋是都有trival condestructor
    template<class ForwardIterator>
    inline void destroy(ForwardIterator first, ForwardIterator last){
        typedef typename _type_traits<ForwardIterator>::is_POD_type is_POD_type;
        _destroy(first, last, is_POD_type());
    }

3.2 内存分配与释放
SGI对内存分配与释放的设计哲学如下:

  • 向system heap申请空间
  • 考虑多线程状态
  • 考虑内存不足时的应变措施
  • 考虑过多“小型区块”可能造成的内存碎片问题(SGI设计了双层级分配器)

C++的内存分配基本操作是::operator new(),内存释放基本操作是::operator delete()。这两个全局函数相当于C的malloc()和free()函数。SGI正是以malloc和free()完成内存的分配与释放

1)两级分配器
考虑到小型区块所可能造成的内存碎片问题,SGI设计了双层级分配器:
在这里插入图片描述

  • 第一级分配器
    • 直接使用malloc()和free()
  • 第二级分配器
    • 当分配区块超过128bytes时,视为“足够大”,调用第一级分配器
    • 当分配区块小于128bytes时,视为“过小”,为了降低额外负担,采用复杂的memory pool整理方式,不再求助于第一级分配器.

第一级配置器

  • 以malloc()、free()、realloc()等C函数执行实际的内存分配、释放、重分配操作
  • 实现出类似C++ new-handler的机制(C++ new-handler机制是,可以要求系统在内存分配需求无法被满足时,调用一个你所指定的函数。换句话说,一旦::operator new无法完成任务,在丢出std::bad_alloc异常状态之前,会先调用由客户指定的处理例程,该处理例程通常即被称为new-handler),不能直接运用C++ new-handler机制,因为它并非使用::operator new来分配内存。

第二级配置器

第二级分配器多了一些机制,避免太多小额区块造成内存的碎片,小额区块存在下列问题:

  • 产生内存碎片

  • 额外负担。额外负担是一些区块信息,用以管理内存。区块越小,额外负担所比例就越大,越显浪费。

  • 当区块大于128bytes时,视为大区块

    • 转交第一级分配器处理
  • 当区块小于128bytes时,视为小额区块

    • 以内存池管理(也称为次层分配):每次分配一大块内存,并维护对应的自由链表(free-list),下次若载有相同大小的内存需求,就直接从free-list中拨出。如果客户释放小额区块,就由分配器回收到free-list中。维护有16个free-list,各自管理大小分别为8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128bytes的小额区块
    • SGI第二级分配器会主动将任何小额区块的内存需求量上调至8的倍数
  • 空间分配函数allocate()

    • 若区块大于128bytes,就调用第一级分配器
    • 若区块小于128bytes,检查对应的free-list
    • 若free-list之内有可用的区块,则直接使用
    • 若free-list之内没有可用区块,将区块大小调至8倍数边界,调用refill(),准备为free-list重新填充空间
      在这里插入图片描述
  • 空间释放函数deallocate()

    • 若区块大于128bytes,就调用第一级分配器
    • 若区块小于128bytes,找出对应的free-list,将区块回收。
      *在这里插入图片描述
  • 重新填充free-list的函数refill()

    • 若free-list中没有可用区块时,会调用chunk_alloc从内存池中申请空间重新填充free-list。缺省申请20个新节点(新区块),如果内存池空间不足,获得的节点数可能小于20。
    • 如果只取了1个节点,直接分给用户,如果多于1个,分给用户一个,将剩余的加入到n对应的free_list中去。
  • chunk_alloc()函数从内存池申请空间,根据end_free-start_free判断内存池中剩余的空间

    • 如果剩余空间充足
      • 直接调出20个区块返回给free-list
    • 如果剩余空间不足以提供20个区块,但足够供应至少1个区块
      • 拨出这不足20个区块的空间
    • 如果剩余空间连一个区块都无法供应
      • 利用malloc()从heap中分配内存(大小为需求量的2倍,加上一个随着分配次数增加而越来越大的附加量),为内存池注入新的可用空间(详细例子见下图)
      • 如果malloc()获取失败,chunk_alloc()就四处寻找有无”尚有未用且区块足够大“的free-list。找到了就挖出一块交出
      • 如果上一步仍未成功,那么就调用第一级分配器,第一级分配器有out-of-memory处理机制,或许有机会释放其它的内存拿来此处使用。如果可以,就成功,否则抛出bad_alloc异常
        *在这里插入图片描述

上图中,一开始就调用chunk_alloc(32,20),于是malloc()分配40个32bytes区块,其中第1个交出,另19个交给free-list[3]维护,余20个留给内存池;接下来客户调用chunk_alloc(64,20),此时free_list[7]空空如也,必须向内存池申请。内存池只能供应(32*20)/64=10个64bytes区块,就把这10个区块返回,第1个交给客户,余9个由free_list[7]维护。此时内存池全空。接下来再调用chunk_alloc(96,20),此时free-list[11]空空如也,必须向内存池申请。而内存池此时也为空,于是以malloc()分配40+n(附加量)个96bytes区块,其中第1个交出,另19个交给free-list[11]维护,余20+n(附加量)个区块留给内存池…

alloc源代码

alloc.h

class alloc{
    private:
        enum EAlign{ ALIGN = 8};//小型区块的上调边界
        enum EMaxBytes{ MAXBYTES = 128};//小型区块的上限,超过的区块由malloc分配
        enum ENFreeLists{ NFREELISTS = (EMaxBytes::MAXBYTES / EAlign::ALIGN)};//free-lists的个数
        enum ENObjs{ NOBJS = 20};//每次增加的节点数
    private:
        //free-lists的节点构造
        union obj{
            union obj *next;
            char client[1];
        };

        static obj *free_list[ENFreeLists::NFREELISTS];
    private:
        static char *start_free;//内存池起始位置
        static char *end_free;//内存池结束位置
        static size_t heap_size;//
    private:
        //将bytes上调至8的倍数
        static size_t ROUND_UP(size_t bytes){
            return ((bytes + EAlign::ALIGN - 1) & ~(EAlign::ALIGN - 1));
        }
        //根据区块大小,决定使用第n号free-list,n从0开始计算
        static size_t FREELIST_INDEX(size_t bytes){
            return (((bytes)+EAlign::ALIGN - 1) / EAlign::ALIGN - 1);
        }
        //返回一个大小为n的对象,并可能加入大小为n的其他区块到free-list
        static void *refill(size_t n);
        //配置一大块空间,可容纳nobjs个大小为size的区块
        //如果配置nobjs个区块有所不便,nobjs可能会降低
        static char *chunk_alloc(size_t size, size_t& nobjs);

    public:
        static void *allocate(size_t bytes);
        static void deallocate(void *ptr, size_t bytes);
        static void *reallocate(void *ptr, size_t old_sz, size_t new_sz);
    };

alloc.cpp

    char *alloc::start_free = 0;
    char *alloc::end_free = 0;
    size_t alloc::heap_size = 0;

    alloc::obj *alloc::free_list[alloc::ENFreeLists::NFREELISTS] = {
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    };

    void *alloc::allocate(size_t bytes){
        if (bytes > EMaxBytes::MAXBYTES){
            return malloc(bytes);
        }
        size_t index = FREELIST_INDEX(bytes);
        obj *list = free_list[index];
        if (list){//此list还有空间给我们
            free_list[index] = list->next;
            return list;
        }
        else{//此list没有足够的空间,需要从内存池里面取空间
            return refill(ROUND_UP(bytes));
        }
    }
    void alloc::deallocate(void *ptr, size_t bytes){
        if (bytes > EMaxBytes::MAXBYTES){
            free(ptr);
        }
        else{
            size_t index = FREELIST_INDEX(bytes);
            obj *node = static_cast<obj *>(ptr);
            node->next = free_list[index];
            free_list[index] = node;
        }
    }
    void *alloc::reallocate(void *ptr, size_t old_sz, size_t new_sz){
        deallocate(ptr, old_sz);
        ptr = allocate(new_sz);

        return ptr;
    }
    //返回一个大小为n的对象,并且有时候会为适当的free list增加节点
    //假设bytes已经上调为8的倍数
    void *alloc::refill(size_t bytes){
        size_t nobjs = ENObjs::NOBJS;
        //从内存池里取
        char *chunk = chunk_alloc(bytes, nobjs);
        obj **my_free_list = 0;
        obj *result = 0;
        obj *current_obj = 0, *next_obj = 0;

        if (nobjs == 1){//取出的空间只够一个对象使用
            return chunk;
        }
        else{
            my_free_list = free_list + FREELIST_INDEX(bytes);
            result = (obj *)(chunk);
            *my_free_list = next_obj = (obj *)(chunk + bytes);
            //将取出的多余的空间加入到相应的free list里面去
            for (int i = 1;; ++i){
                current_obj = next_obj;
                next_obj = (obj *)((char *)next_obj + bytes);
                if (nobjs - 1 == i){
                    current_obj->next = 0;
                    break;
                }
                else{
                    current_obj->next = next_obj;
                }
            }
            return result;
        }
    }
    //假设bytes已经上调为8的倍数
    char *alloc::chunk_alloc(size_t bytes, size_t& nobjs){
        char *result = 0;
        size_t total_bytes = bytes * nobjs;
        size_t bytes_left = end_free - start_free;

        if (bytes_left >= total_bytes){//内存池剩余空间完全满足需要
            result = start_free;
            start_free = start_free + total_bytes;
            return result;
        }
        else if (bytes_left >= bytes){//内存池剩余空间不能完全满足需要,但足够供应一个或以上的区块
            nobjs = bytes_left / bytes;
            total_bytes = nobjs * bytes;
            result = start_free;
            start_free += total_bytes;
            return result;
        }
        else{//内存池剩余空间连一个区块的大小都无法提供
            size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
            if (bytes_left > 0){
                obj **my_free_list = free_list + FREELIST_INDEX(bytes_left);
                ((obj *)start_free)->next = *my_free_list;
                *my_free_list = (obj *)start_free;
            }
            start_free = (char *)malloc(bytes_to_get);
            if (!start_free){
                obj **my_free_list = 0, *p = 0;
                for (int i = 0; i <= EMaxBytes::MAXBYTES; i += EAlign::ALIGN){
                    my_free_list = free_list + FREELIST_INDEX(i);
                    p = *my_free_list;
                    if (p != 0){
                        *my_free_list = p->next;
                        start_free = (char *)p;
                        end_free = start_free + i;
                        return chunk_alloc(bytes, nobjs);
                    }
                }
                end_free = 0;
            }
            heap_size += bytes_to_get;
            end_free = start_free + bytes_to_get;
            return chunk_alloc(bytes, nobjs);
        }
      }

3.3 内存基本处理工具

STL定义了5个全局函数,作用于未初始化空间上,有助于容器的实现:

  • 作用于单个对象(见3.1 对象构造与析构,SGI STL定义在头文件<stl_construct.h>中)
    • construct()函数(构造单个对象)
    • destroy()函数(析构单个对象)
  • 作用于容器的区间(本节,SGI STL定义在头文件<stl_uninitialized.h>中,是高层copy()、fill()、fill_n()的底层函数)
    • uninitialized_copy()函数
    • uninitialized_fill()函数
    • uninitialized_fill_n()函数

容器的全区间构造函数通常分2步:

  1. 分配内存区块,足以包含范围内的所有元素
  2. 调用上述3个函数在全区间范围内构造对象(因此,这3个函数使我们能够将内存的分配与对象的构造行为分离;并且3个函数都具有”commit or rollback“语意,要么所有对象都构造成功,要么一个都没有构造)

源代码

template<class InputIterator, class ForwardIterator>
    ForwardIterator _uninitialized_copy_aux(InputIterator first, InputIterator last,
        ForwardIterator result, _true_type);
    template<class InputIterator, class ForwardIterator>
    ForwardIterator _uninitialized_copy_aux(InputIterator first, InputIterator last,
        ForwardIterator result, _false_type);

    template<class InputIterator, class ForwardIterator>
    ForwardIterator uninitialized_copy(InputIterator first, InputIterator last, ForwardIterator result){
        typedef typename _type_traits<iterator_traits<InputIterator>::value_type>::is_POD_type isPODType;
        return _uninitialized_copy_aux(first, last, result, isPODType());
    }
    template<class InputIterator, class ForwardIterator>
    ForwardIterator _uninitialized_copy_aux(InputIterator first, InputIterator last,
        ForwardIterator result, _true_type){
        memcpy(result, first, (last - first) * sizeof(*first));
        return result + (last - first);
    }
    template<class InputIterator, class ForwardIterator>
    ForwardIterator _uninitialized_copy_aux(InputIterator first, InputIterator last,
        ForwardIterator result, _false_type){
        int i = 0;
        for (; first != last; ++first, ++i){
            construct((result + i), *first);
        }
        return (result + i);
    }

    /***************************************************************************/
    template<class ForwardIterator, class T>
    void _uninitialized_fill_aux(ForwardIterator first, ForwardIterator last,
        const T& value, _true_type);
    template<class ForwardIterator, class T>
    void _uninitialized_fill_aux(ForwardIterator first, ForwardIterator last,
        const T& value, _false_type);

    template<class ForwardIterator, class T>
    void uninitialized_fill(ForwardIterator first, ForwardIterator last, const T& value){
        typedef typename _type_traits<T>::is_POD_type isPODType;
        _uninitialized_fill_aux(first, last, value, isPODType());
    }
    template<class ForwardIterator, class T>
    void _uninitialized_fill_aux(ForwardIterator first, ForwardIterator last,
        const T& value, _true_type){
        fill(first, last, value);
    }
    template<class ForwardIterator, class T>
    void _uninitialized_fill_aux(ForwardIterator first, ForwardIterator last,
        const T& value, _false_type){
        for (; first != last; ++first){
            construct(first, value);
        }
    }

    /***************************************************************************/
    template<class ForwardIterator, class Size, class T>
    ForwardIterator _uninitialized_fill_n_aux(ForwardIterator first,
        Size n, const T& x, _true_type);
    template<class ForwardIterator, class Size, class T>
    ForwardIterator _uninitialized_fill_n_aux(ForwardIterator first,
        Size n, const T& x, _false_type);

    template<class ForwardIterator, class Size, class T>
    inline ForwardIterator uninitialized_fill_n(ForwardIterator first,
        Size n, const T& x){
        typedef typename _type_traits<T>::is_POD_type isPODType;
        return _uninitialized_fill_n_aux(first, n, x, isPODType());
    }
    template<class ForwardIterator, class Size, class T>
    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){
        int i = 0;
        for (; i != n; ++i){
            construct((T*)(first + i), x);
        }
        return (first + i);
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值