STL源码剖析 — 空间配置器(allocator)

前言



  以STL的实现角度而言,第一个需要介绍的就是空间配置器,因为整个STL的操作对象都存放在容器之中。

  你完全可以实现一个直接向硬件存取空间的allocator。

  下面介绍的是SGI STL提供的配置器,配置的对象,是内存。(以下内容来自《STL源码剖析》)

空间配置器的标准接口


根据STL的规范,allocator的必要接口

  • 各种typedef

    1 allocator::value_type
    2 allocator::pointer
    3 allocator::const_pointer
    4 allocator::reference
    5 allocator::const_reference
    6 allocator::size_type
    7 allocator::difference_type
    8 allocator::rebind // class rebind<U>拥有唯一成员other;是一个typedef,代表allocator<U>
  • 默认构造函数和析构函数,因为没有数据成员,所以不需要初始化,但是必须被定义
    1 allocator::allocator()
    2 allocator::allocator(const allocator&)
    3 template <class U> allocator::allocator(const allocator<U>&)
    4 allocator::~allocator()
  • 初始化,地址相关函数
    1 // 配置空间,足以存储n个T对象,第二个参数是提示,能增进区域性
    2 pointer allocator::allocate(size_type n, const void*=0)
    3
    4 size_type allocator::max_size() const
    5
    6 pointer allocator::address(reference x) const
    7 const_pointer allocator::address(const_reference x) const
  • 构建函数

    1 void allocator::construct(pointer p, const T& x)
    2 void allocator::destory(pointer p)

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

 1 #ifndef __VIGGO__
 2 #define  __VIGGO__
 3 #include <new>        // for placement new
 4 #include <cstddef>    // for ptrdiff_t, size_t
 5 #include <cstdlib>    // for exit()
 6 #include <climits>    // for UINT_MAX
 7 #include <iostream>    // for cerr
 8
 9 namespace VG {
10
11     template <class T>
12     inline T* _allocate(ptrdiff_t n, T*) {
13         set_new_handler(0);
14         T* tmp = (T*)(::operator new((size_t)(n * sizeof(T))));
15         if (tmp == 0) {
16             cerr << "alloc memory error!" << endl;
17             exit(1);
18         }
19         return tmp;
20     }
21
22     template <class T>
23     inline void _deallocate(T* p) {
24         ::operator delete(p);
25     }
26
27     template <class T1, class T2>
28     inline void _construct(T1* p, const T2& value) {
29         new(p) T1(value);
30     }
31
32     template <class T>
33     inline void _destroy(T* p) {
34         p->~T();
35     }
36
37     template <class T>
38     class allocator {
39     public:
40         typedef T            value_type;
41         typedef T*            pointer;
42         typedef const T*    const_pointer;
43         typedef T&            reference;
44         typedef const T&    const_reference;
45         typedef size_t        size_type;
46         typedef ptrdiff_t    difference_type;
47
48         template <class U>
49         struct rebind {
50             typedef allocator<U> other;
51         };
52
53         pointer address(reference x) {return (pointer)&x;}
54         const_pointer address(const_reference x) const {
55             return (const_pointer)&x;
56         }
57
58         pointer allocate(size_type n, const void *hint=0) {
59             return _allocate((difference_type)n, (pointer)0); // mark
60         }
61
62         void deallocate(pointer p, size_type n) {
63             _deallocate(p);
64         }
65
66         size_type max_size() const {return size_type(UINT_MAX / sizeof(T));}
67
68         void construct(pointer p, const T& x) {
69             _construct(p, x);
70         }
71
72         void destroy(pointer p) {
73             _destroy(p);
74         }
75     };
76 }
77 #endif

  放在 vector<int, VG::allocator<int> > 中测试,可以实现简单的内存分配,但是实际上的 allocator 要比这个复杂。

SGI特殊的空间配置器



  标准的allocator只是基层内存配置/释放行为(::operator new 和 ::operator delete)的一层薄薄的包装,并没有任何效率上的强化。

  现在我们看看C++内存配置和释放是怎样做的:

  new运算分两阶段(1)调用 ::operator new 配置内存;(2) 调用对象构造函数构造对象内容。

  delete运算也分两阶段(1) 调用对象的析构函数;(2)调用 ::operator delete 释放内存。

  为了精密分工,STL allocator决定将两阶段操作区分开来,内存配置由 alloc::allocate() 负责。内存释放操作由 alloc::deallocate()负责;对象构造由 ::construct() 负责,对象析构由 ::destroy() 负责。

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



  construct() 接受一个指针p和一个初值value,该函数的用途就是将初值设定到指针所指的空间上。C++的placement new运算子可用来完成这一任务。

  destory()有两个版本,一是接受一个指针,直接调用该对象的析构函数即可。另外一个接受first和last,将半开范围内的所有对象析构。首先我们不知道范围有多大,万一很大,而每个对象的析构函数都无关痛痒(所谓 trivial destructor),那么一次次调用这些无关痛痒的析构函数是一种浪费。所以我们首先判断迭代器所指对象是否为 trivial(无意义), 是则什么都不用做;否则一个个调用析构。

上图为construct的实现函数

上图为destroy的实现函数

这里用到我们神奇的 __type_traits<T>,之前介绍的 traits 是 萃取返回值类型 和 作为重载依据的,现在为每一个内置类型特化声明一些tag。

现在我们需要用到 两个标志:

示例:

空间的配置和释放:std::alloc



  SGI的设计哲学: 1. 向 system heap 要求空间; 2. 考虑多线程状态(先略过);3. 考虑内存不足时的应变措施;4. 考虑过多“小型区块”可能造成的内存碎片问题。

  SGI设计了双层级配置器,第一级配置器直接使用 malloc() 和 free(),第二级配置器则视情况采用不同的策略;当配置区块超过128bytes时,交给第一级配置器。

  整个设计究竟只开放第一级配置器,或是同时开放第二级配置,取决于__USE_MALLOC时候被定义:

1 # ifdef __USE_MALLOC
2 ...
3 typedef __malloc_alloc_template<0> malloc_alloc;
4 typedef malloc_alloc alloc; // 令alloc为第一级配置器
5 #else
6 ...
7 // 令alloc为第二级配置器
8 typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0>alloc;
9 #endif

  其中__malloc_alloc_template就是第一级配置器,__default_alloc_template为第二级配置器。alloc并不接受任何template型别参数。

  无论alloc被定义为第一级或第二级配置器,SGI还为它在包装一个接口如下,使配置器的接口能够符合STL规格:

 1 template <class T, class Alloc>
 2 class simple_alloc {
 3 public:
 4        static T *allocate(size_t n)
 5             {return 0==n? 0 : (T*)Alloc::allocate(n * sizeof(T));}
 6        static T *allocate(void)
 7             {return (T*)Alloc::allocate(sizeof(T));}
 8        static void deallocate(T *p, size_t n)
 9             {if (0 != n) Alloc::deallocate(p, n*sizeof(T));}
10        static void deallocate(T *p)
11             {Alloc::deallocate(p, sizeof(T));}

  一二级配置器的关系,接口包装,及实际运用方式,

第一级配置器 __malloc_alloc_template



 1 #if 0
 2 #    include <new>
 3 #    define __THROW_BAD_ALLOC throw bad_alloc
 4 #elif !defined(__THROW_BAD_ALLOC)
 5 #    include <iostream>
 6 #    define __THROW_BAD_ALLOC cerr << "out of memery" << endl; exit(1);
 7 #endif
 8
 9 // malloc-based allocator.通常比稍后介绍的 default alloc 速度慢
10 // 一般而言是thread-safe,并且对于空间的运用比较高效
11 // 以下是第一级配置器
12 // 注意,无“template型别参数”。置于“非型别参数”inst,则完全没排上用场
13 template <int inst>
14 class __malloc_alloc_template {
15 private:
16     //以下都是函数指针,所代表的函数将用来处理内存不足的情况
17     static void *oom_malloc(size_t);
18     static void *oom_realloc(void*, size_t);
19     static void (* __malloc_alloc_oom_handler)();
20 public:
21     static void * allocate(size_t n) {
22         void *result = malloc(n); // 第一级配置器直接使用malloc
23         // 无法满足需求时,改用oom_malloc
24         if (0 == result) result = oom_malloc(n);
25         return result;
26     }
27
28     static void deallocate(void *p, size_t /* n */) {
29         free(p); // 第一级配置器直接用free()
30     }
31
32     static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz) {
33         void *result = realloc(p, new_sz);
34         if (0 == result) result = oom_realloc(p, new_sz);
35         return result;
36     }
37
38     // 以下仿真C++的 set_handler()。换句话,你可以通过它
39     // 指定自己的 out-of-memory handler,企图释放内存
40     // 因为没有调用 new,所以不能用 set_new_handler
41     static void (* set_malloc_handler(void (*f)())) () {
42         void (*old)() = __malloc_alloc_oom_handler;
43         __malloc_alloc_oom_handler = f;
44         return old;
45     }
46 };
47
48 // 初值为0,待定
49 template <int inst>
50 void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0;
51
52 template <int inst>
53 void * __malloc_alloc_template<inst>::oom_malloc(size_t n) {
54     void (* my_malloc_handler)();
55     void *result;
56
57     for (;;) {
58         my_malloc_handler = __malloc_alloc_oom_handler;
59         if (0 = my_malloc_handler) {__THROW_BAD_ALLOC;} // 如果没设置
60         (* my_malloc_handler)(); // 调用处理例程,企图释放内存
61         result = malloc(n);        // 再次尝试配置内存
62         if (result) return result;
63     }
64 }
65
66 template <int inst>
67 void * __malloc_alloc_template<inst>::oom_realloc(void *p, size_t n) {
68     void (* my_malloc_handler)();
69     void *result;
70
71     for (;;) {
72         my_malloc_handler = __malloc_alloc_oom_handler;
73         if (0 == my_malloc_handler) {__THROW_BAD_ALLOC;}
74         (*my_malloc_handler)();
75         result = realloc(p, n);
76         if (result) return result;
77     }
78 }

  

  

  

时间: 2024-10-04 18:54:26

STL源码剖析 — 空间配置器(allocator)的相关文章

STL源码剖析——空间配置器Allocator#2 一/二级空间配置器

上节学习了内存配置后的对象构造行为和内存释放前的对象析构行为,在这一节来学习内存的配置与释放. C++的内存配置基本操作是::operator new(),而释放基本操作是::operator delete().这两个全局函数相当于C的malloc() 和free() 函数.而SGI正是以malloc() 和free() 完成内存的配置与释放. 考虑到小型区块可能造成的内存破碎问题,SGI设计了两级的空间配置器.第一级直接使用malloc() 和free() ,而第二级则视情况采用不同的策略:当

STL源码剖析——空间配置器Allocator#3 自由链表与内存池

上节在学习第二级配置器时了解了第二级配置器通过内存池与自由链表来处理小区块内存的申请.但只是对其概念进行点到为止的认识,并未深入探究.这节就来学习一下自由链表的填充和内存池的内存分配机制. refill()函数——重新填充自由链表 前情提要,从上节第二级配置器的源码中可以看到,在空间配置函数allocate()中,当所需的某号自由链表为空时,才会调用refill()函数来填充链表.refill()函数默认申请20块区块的内存(5行),但所得内存不一定就是20块,要看当前内存池的剩余情况和堆容量的

STL源码剖析 --- 空间配置器 std::alloc

STL是建立在泛化之上的.数组泛化为容器,参数化了所包含的对象的类型.函数泛化为算法,参数化了所用的迭代器的类型.指针泛化为迭代器,参数化了所指向的对象的类型.STL中的六大组件:容器.算法.迭代器.配置器.适配器.仿函数. 这六大组件中在容器中分为序列式容器和关联容器两类,正好作为STL源码剖析这本书的内容.迭代器是容器和算法之间的胶合剂,从实现的角度来看,迭代器是一种将operator*.operator->.operator++.operator-等指针相关操作予以重载的class tem

STL源码剖析--空间配置器

STL的设计非常巧妙,组件间互取短长,形成了一个世界,这是这个世界里的组件: 1. containers(容器):所谓容器,是指存放数据的地方,将数据以一定的方法组织存放.根据不同的组织方式,可以把容器分为顺序容器,如vector.deque.list,关联容器,如set.map.Container是一种class template. 2. algorithm(算法):各种常用不常用的算法如sort.copy.search等等.algorithm是一种function template. 3.

STL源码分析--空间配置器的底层实现 (二)

STL源码分析-空间配置器 空间配置器中门道 在STL中的容器里都是使用统一的空间配置器,空间配置器就是管理分配内存和销毁内存的.在STL将在heap空间创建一个对象分为两个步骤,第一是申请一块内存,第二是在这块内存中初始化一个对象.首先申请空间是由malloc提供,初始化一个对象时由constructor管理.销毁一个对象也是由两步骤完成,第一是销毁空间上的对象,第二是释放这块内存. 同时,STL的空间配置器分为两级内存,如果申请的内存空间大于128KB,那么就使用第一级空间配置,如果小于,那

STL源码分析--空间配置器 第一级配置器

一.SGI STL配置器简介 SGI STL的配置器与众不同,它与标准规范不同.如果要在程序中明确使用SGI配置器,那么应该这样写: [cpp] view plaincopyprint? vector<int,std::alloc> iv; 他的名字是alloc,而且不接受任何参数.标准配置器的名字是allocator,而且可以接受参数. SGI STL的每一个容器都已经指定了缺省配置器:alloc.我们很少需要自己去指定空间配置器.比如vector容器的声明: [cpp] view plai

STL源码剖析——空间的配置与释放

C++的内存配置基本操作是  ::operator new(),内存释放的基本操作是 ::operator delete().这两个全局函数相当于C的malloc()和free()函数.是的,正是如此,STL正是以malloc()和free()完成内存的配置与释放. 但是考虑到小型区块所可能造成的内存破碎问题,STL中设计了双层级配置器, 第一级配置器直接使用malloc()和free(),第二级配置器则视情况采用不用的策略:当配置区块超过128bytes时,视之为"足够大",便调用第

STL源码剖析---根据最新版本的g++4.9.0(支持C++11)的修订(1)空间配置器

源码剖析采用的G++版本为2.91.57版本,是比较老的版本与最新版本4.9.0有某些方面的差别.现在我针对最新版本做一个分析.我下载了最新的gcc-4.9.0的包作为观察对象: 我们#include <>时的头文件放在:gcc-4.9.0/libstdc++-v3/include/std:例如vector. 真正的实现文件放在:gcc-4.9.0/libstdc++-v3/include/bits:例如:stl_vector,注意前面的stl_. 最后要说的是:技术是不断进步,不断发展变化的

《STL源码剖析》空间配置器

空间配置器(allocator) 空间配置器按我的理解就是C++ STL进行内存管理的组件(包括内存的申请和释放):当然,不只是内存,还可以向硬盘申请空间: 我主要看了内存的配置与释放(这里"配置"就应该是"申请"的意思).STL对此设计的哲学主要包括以下四个方面: 1.向系统堆空间申请内存空间 2.考虑了多线程的情况下的申请: 3.考虑内存不足的应变措施: 4.考虑过多"小型区块"的内存碎片的问题: C++ 申请的基本动作是::operator