在stl源码剖析里面叫空间配置器。

说明

~~~~    在C++编程中,分配器(英语:allocator)是C++标准库的重要组成部分。C++的库中定义了多种被统称为“容器”的数据结构(如链表、集合等),这些容器的共同特征之一,就是其大小可以在程序的运行时改变;为了实现这一点,进行动态内存分配就显得尤为必要,在此分配器就用于处理容器对内存的分配与释放请求。换句话说,分配器用于封装标准模板库(STL)容器在内存管理上的低层细节。默认情况下,C++标准库使用其自带的通用分配器,但根据具体需要,程序员也可自行定制分配器以替代之。

~~~~    分配器最早由亚历山大·斯特潘诺夫作为C++标准模板库(Standard Template Library,简称STL)的一部分发明,其初衷是创造一种能“使库更加灵活,并能独立于底层数据模型的方法”,并允许程序员在库中利用自定义的指针和引用类型;但在将标准模板库纳入C++标准时,C++标准委员会意识到对数据模型的完全抽象化处理会带来不可接受的性能损耗,为作折中,标准中对分配器的限制变得更加严格,而有鉴于此,与斯特潘诺夫原先的设想相比,现有标准所描述的分配器可定制程度已大大受限。

~~~~    虽然分配器的定制有所限制,但在许多情况下,仍需要用到自定义的分配器,而这一般是为封装对不同类型内存空间(如共享内存与已回收内存)的访问方式,或在使用内存池进行内存分配时提高性能而为。除此以外,从内存占用和运行时间的角度看,在频繁进行少量内存分配的程序中,若引入为之专门定制的分配器,也会获益良多。

使用需求

~~~~    任意满足分配器使用需求的C++类都可作分配器使用。具体来说,当一个类(在此设为类A)有为一个特定类型(在此设为类型T)的对象分配内存的能力时,该类就必须提供以下类型的定义:

  • A::pointer指针
  • A::const_pointer常量指针
  • A::reference引用
  • A::const_reference常量引用
  • A::value_type值类型
  • A::size_type所用内存大小的类型,表示类A所定义的分配模型中的单个对象最大尺寸的无符号整型
  • A::difference_type指针差值得类型,为带符号整型,用于表示分配模型内的两个指针的差值。

~~~~    如此才能以通用的方式声明对象与对该类对象的引用T。allocator提供这些指针或者引用的类型定义的初衷,是隐蔽指针或者引用的物理实现细节;因为在16位编程时代,远指针是与普通指针非常不同的,allocator可以定义一些结构来表示这些指针或引用,而容器类用户不需要了解其实如何实现的。

远指针:
远指针不是让编译程序把程序数据段地址作为指针的段地址部分,而是把指针的段地址与指针的偏移量直接存放在指针内。
在分段体系结构计算机中,远指针是包括段选择器的指针,使得可以指向默认段之外的地址。
远指针的比较和算法是有问题的:可以有几个不同的段偏移地址对指向一个 物理地址。

~~~~    虽然按照标准,在库的实现过程中允许假定分配器(类)A的A::pointer和A::const_pointer即是对T与T const的简单的类型定义,但一般更鼓励支持通用分配器。
~~~~    另外,设有对于为某一对象类型T所设定的分配器A,则A必须包含四项成员函数,分别为,分配函数、解除分配函数、最大个数函数和地址函数:

  • A::pointer A::allocate(size_type n, A::const_pointer hint = 0)。分配函数用以进行内存分配。其中调用参数n即为需要分配的对象个数,另一调用参数hint(须为指向已为A所分配的某一对象的指针)则为可选参数,可用于放分配过程中指定新数组所在的内存地址,以提高引用局部性,但在实际的分配过程中程序也可以根据情况自动忽略掉该参数。该函数调用时会返回指向分配所得的新数组的第一个元素的指针,而这一数组的大小足以容纳n个T类元素。在此需要注意的是,调用时只为此数组分配了内存,而并未实现构造对象。
  • void A::deallocate(A:L:pointer p, A::size_type n)。解除分配函数。其中p为需要解除分配的对象指针(以A::allocate函数所返回的指针做参数),n为对象个数,而调用该函数时即是将以p起始的n个元素解除分配,但通时并不会析构之。C++标准明确要求在调用deallocate之前,改地址空间上的对象已经被析构。
  • A::max_size(),最大个数函数。返回A::allocate一次调用所能成功分配的元素的最大个数,其返回值等价于A::size_t(-1)/sizeof(T)的结果。A::size_t(-1)获取的是该系统的最大值。
  • A::pointer A::address(reference x),地址函数。调用时返回一个指向x的指针。

~~~~    除此之外,由于对象的构造/析构过程与分配/解除分配过程分别进行,因而分配器还需要成员函数A::constructA::constructA::construct(构造函数)与A::destoryA::destoryA::destory(析构函数)以对对象进行构造和析构,且两者应等价于如下函数:

  • template <\typename T> void A::construct(A::pointer p, A::const_reference t) { new ((void*) p) T(t); }
  • template <\typename T> void A::destroy(A::pointer p){ ((T*)p)->~T(); }

~~~~    以上代码中使用了placement new语法,且直接调用了析构函数。
~~~~    分配器应该是可复制拷贝的,任举一个例子,为T类对象而设的分配器可以由另一为U类所设的分配器构造。若分配器分配了一段存储空间,则这段存储空间只能由与该分配器等价的分配器解除分配。
~~~~    分配器还需要提供一个模板类成员template <typename U> struct A::rebind { typedef A <U> other;},以模板(C++)参数化的方式,借之来针对不同的数据类型获取不同的分配器。例如,若给定某一为整形而设的相关分配器。实际上,std::list <int>实际要分配的是包含了双向链表指针的node <int>,而不是实际分配int类型,这是引入了rebind的初衷。
~~~~    与分配器相关联的operator ==,仅当一个allocator分配的内存可以被另一个allocator释放时,上诉相等比较算符返回真。operator !=的返回结果与之相反。

自定义分配器

~~~~    定义自定义分配器的主要原因之一是提升性能。利用专用的自定义分配器可以提高程序的性能,又或提高内存使用效率,亦或者两者兼而有之。默认分配器使用new操作费分配存储空间,而这常利用C语言堆分配函数(malloc())实现。由于堆分配函数常针对偶发的内存大量分配作优化,因此在为需要一次分配大量内存的容器(如向量、双端队列)分配内存时,默认分配器一般效率良好。但是,对于关联容器与双向链表这类需要频繁分配少量内存的容器来说,若采用默认的分配器分配内存,则通常效率很低。除此之外,基于malloc()的默认分配器还存在很多问题,诸如较差的引用局部性,以及可能造成内存碎片化。
~~~~    有鉴于此,在这一情况下,人们常使用基于内存池的分配器来解决频繁少量分配问题。与默认的“按需分配”方式不同,在使用基于内存池的分配器时,程序会预先为止分配大块内存(即内存池),而后在需要分配内存时,自定义分配器只需要向请求方返回一个指向池内内存的指针即可;在对象析构时,并不需要实际接触分配的内存,而是延迟到内存池的生命周期完结才真正接触分配。
~~~~    另外,在《C++程序设计语言》中,比雅尼·斯特劳斯特鲁普则认为“‘严格限制分配器,以免各对象信息不同’,这点显然问题不大”(大意),并指出大部分分配器并不需要状态,甚至没有状态情形下性能反倒更佳。他提出了三个自定义分配器的用例:内存池型的分配器、共享内存型分配器与垃圾回收型分配器,并展示了一个分配器的实现,此间利用了一个内部内存池,以快速分配/解除分配少量内存。但他也提到,如此优化可能已经在他所提供的样例分配器中实现。

~~~~    自定义分配器的另一用途是调试内存相关错误。若要做到这一点,可以编写一个分配器,令之在分配时分配额外的内存,并借此存放调试信息。这类分配器不仅可以保证内存由同类分配器分配/解除分配内存,还可在一定程度上保护程序免受缓存溢出之害。
~~~~    C++之父 Bjarne stroustrup 提到,自定义分配器的三个主要应用,即内存池分配器,共享内存分配器和垃圾收集(gc)分配器
~~~~    分享一个例子:设计一个简单地内存分配器(空间分配器)

使用方法

~~~~    当初始化标准容器时,若使用自定义分配器,则可将其写入模板参数,以替代默认的std::allocator,如下所示:

namespace std {//vactortemplate <class T, class Allocator = allocator<T> > class vector;
// ...//队列
template<class _Ty,class _Container = deque<_Ty> >class queue

~~~~    正如其他所有C++类模板般,在初始化同一个标准库容器时,若使用了不同的分配器,则所生成容器的类型亦不同。譬如,若函数需一整型向量数组std::vector <int>作为参数,则其只能接受由默认分配器生成的整型向量数组。

默认分配器

我们先不考虑怎么写自定义的分配器,我们先看一看stl提供的allocator,我们先看看它是怎么实现的,先上源码,来自gcc5.4版本,看代码我们会发现,默认分配器真正的实现是在new_allocator.h头文件中做的,头文件具体调用顺序为:bits/allocator.h -> bits/c++allocator.h(有时候找不到) -> new_allocator.h, 看代码我们知道,在前面两个头文件中基本没做什么,真正的实现为模板类new_allocator。具体目录在/usr/include/c++/5/下面:

qilimi@qilimi-desktop:/usr/include/c++/5$ find ./ -name "*allocator*"
./ext/array_allocator.h
./ext/extptr_allocator.h
./ext/malloc_allocator.h
./ext/pool_allocator.h
./ext/new_allocator.h
./ext/throw_allocator.h
./ext/bitmap_allocator.h
./ext/mt_allocator.h
./ext/debug_allocator.h
./bits/uses_allocator.h
./bits/allocator.h
./scoped_allocator
qilimi@qilimi-desktop:/usr/include/c++/5$

这里贴一下c++allocator.h文件的内容:

#ifndef _GLIBCXX_CXX_ALLOCATOR_H
#define _GLIBCXX_CXX_ALLOCATOR_H 1#include <ext/new_allocator.h>#if __cplusplus >= 201103L
namespace std
{/***  @brief  An alias to the base class for std::allocator.*  @ingroup allocators**  Used to set the std::allocator base class to*  __gnu_cxx::new_allocator.**  @tparam  _Tp  Type of allocated object.*/template<typename _Tp>using __allocator_base = __gnu_cxx::new_allocator<_Tp>;
}
#else
// Define new_allocator as the base class to std::allocator.
# define __allocator_base  __gnu_cxx::new_allocator
#endif#endif

我们这里就看默认分配器new_allocator.h里面:

#ifndef _NEW_ALLOCATOR_H
#define _NEW_ALLOCATOR_H 1#include <bits/c++config.h>
#include <new>
#include <bits/functexcept.h>
#include <bits/move.h>
#if __cplusplus >= 201103L
#include <type_traits>
#endifnamespace __gnu_cxx _GLIBCXX_VISIBILITY(default)
{_GLIBCXX_BEGIN_NAMESPACE_VERSIONusing std::size_t;using std::ptrdiff_t;/***  @brief  An allocator that uses global new, as per [20.4].*  @ingroup allocators**  This is precisely the allocator defined in the C++ Standard.*    - all allocation calls operator new*    - all deallocation calls operator delete**  @tparam  _Tp  Type of allocated object.*/template<typename _Tp>class new_allocator{public:typedef size_t     size_type;typedef ptrdiff_t  difference_type;typedef _Tp*       pointer;typedef const _Tp* const_pointer;typedef _Tp&       reference;typedef const _Tp& const_reference;typedef _Tp        value_type;template<typename _Tp1>struct rebind{ typedef new_allocator<_Tp1> other; };#if __cplusplus >= 201103L// _GLIBCXX_RESOLVE_LIB_DEFECTS// 2103. propagate_on_container_move_assignmenttypedef std::true_type propagate_on_container_move_assignment;
#endif_GLIBCXX20_CONSTEXPRnew_allocator() _GLIBCXX_USE_NOEXCEPT { }_GLIBCXX20_CONSTEXPRnew_allocator(const new_allocator&) _GLIBCXX_USE_NOEXCEPT { }template<typename _Tp1>_GLIBCXX20_CONSTEXPRnew_allocator(const new_allocator<_Tp1>&) _GLIBCXX_USE_NOEXCEPT { }~new_allocator() _GLIBCXX_USE_NOEXCEPT { }pointeraddress(reference __x) const _GLIBCXX_NOEXCEPT{ return std::__addressof(__x); }const_pointeraddress(const_reference __x) const _GLIBCXX_NOEXCEPT{ return std::__addressof(__x); }// NB: __n is permitted to be 0.  The C++ standard says nothing// about what the return value is when __n == 0._GLIBCXX_NODISCARD pointerallocate(size_type __n, const void* = static_cast<const void*>(0)){if (__n > this->max_size())std::__throw_bad_alloc();#if __cpp_aligned_newif (alignof(_Tp) > __STDCPP_DEFAULT_NEW_ALIGNMENT__){std::align_val_t __al = std::align_val_t(alignof(_Tp));return static_cast<_Tp*>(::operator new(__n * sizeof(_Tp), __al));}
#endifreturn static_cast<_Tp*>(::operator new(__n * sizeof(_Tp)));}// __p is not permitted to be a null pointer.voiddeallocate(pointer __p, size_type){#if __cpp_aligned_newif (alignof(_Tp) > __STDCPP_DEFAULT_NEW_ALIGNMENT__){::operator delete(__p, std::align_val_t(alignof(_Tp)));return;}
#endif::operator delete(__p);}size_typemax_size() const _GLIBCXX_USE_NOEXCEPT{#if __PTRDIFF_MAX__ < __SIZE_MAX__return size_t(__PTRDIFF_MAX__) / sizeof(_Tp);
#elsereturn size_t(-1) / sizeof(_Tp);
#endif}#if __cplusplus >= 201103Ltemplate<typename _Up, typename... _Args>voidconstruct(_Up* __p, _Args&&... __args)noexcept(noexcept(::new((void *)__p)_Up(std::forward<_Args>(__args)...))){ ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }template<typename _Up>voiddestroy(_Up* __p)noexcept(noexcept( __p->~_Up())){ __p->~_Up(); }
#else// _GLIBCXX_RESOLVE_LIB_DEFECTS// 402. wrong new expression in [some_] allocator::constructvoidconstruct(pointer __p, const _Tp& __val){ ::new((void *)__p) _Tp(__val); }voiddestroy(pointer __p) { __p->~_Tp(); }
#endiftemplate<typename _Up>friend booloperator==(const new_allocator&, const new_allocator<_Up>&)_GLIBCXX_NOTHROW{ return true; }template<typename _Up>friend booloperator!=(const new_allocator&, const new_allocator<_Up>&)_GLIBCXX_NOTHROW{ return false; }};_GLIBCXX_END_NAMESPACE_VERSION
} // namespace#endif

我们看一下分配和释放的接口allocate和deallocate,实现上只是单纯的将::operator new和::operator delete进行了一下封装,没用做特殊处理。

当然,如果我们不特别说明,直接在Linux系统下使用标准C++的stl容器,在为容器元素分配内存时,使用的方式就是类似于正常的new和delete,这种方式对于不频繁分配,并且一次分配大块内存的使用情况是适用的,典型的容器像vector和deque。
其中有些东西,前面写了就不多说了,主要还是allocate和deallocat函数。
重写就是主要重写allocate和deallocat函数,像实内存池。
看了内存分配器,我就发现了我之前几个项目的不足之处吧,尤其是有一个现在在用的一个分布式计算,他的容器的分配器确实应该自定义,因为有频繁申请,加入内存池还是很有必要的。

扩展分配器

在前面find,我们就发现了很多的xx_allocator.h文件,这些就是扩展分配器,下面是比较经典的:

  • __pool_alloc :比较出名的SGI内存池分配器
  • __mt_alloc : 多线程内存池分配器
  • array_allocator : 全局内存分配,只分配不释放,交给系统来释放
  • malloc_allocator : 封装了一下std::malloc和std::free

很多时候我们需要自定义分配器,我们都可以学习这些扩展分配器,来进行模仿和借鉴。

这些我之前没有认真学,之后会重新学习,等学完了,就会写出来,立下flag,才知道有多少事情没有做,不然就忘了。
(╯°Д°)╯︵┻━┻

C++ 分配器 allocator相关推荐

  1. C++STL学习笔记(4) 分配器(Allocator)

    在前面的博客<C++ STL学习笔记(3) 分配器Allocator,OOP, GP简单介绍>中,简单的介绍了分配器再STL的容器中所担当的角色,这一节对STL六大部件之一的分配器进行详细 ...

  2. 分配器allocator和new重载

    3. 分配器allocator和new重载 3 分配器allocator和new重载 3.3 分配器allocator详解 3.4 自定义allocator 3.5 未初始化内存复制分析 3 分配器a ...

  3. C++: STL内存分配器--allocator

    STL内存分配器--allocator 一.STL内存分配器 二.STL allocator 一.STL内存分配器 分配器(allocator))是C ++标准库的一个组件, 主要用来处理所有给定容器 ...

  4. C++ STL学习笔记(3) 分配器Allocator,OOP, GP简单介绍

    继续学习侯捷老师的课程! 在前面的博客<C++ STL学习笔记(2) 容器结构与分类>中介绍了STL中常用到的容器以及他们的使用方法,在我们使用容器的时候,背后需要一个东西支持对内存的使用 ...

  5. STL源码:分配器 allocator

    operator new() 和 malloc() operator new()就是调用malloc来申请内存空间 所有的分配内存操作最终都将落在 malloc 上.malloc分配的实际内存要比申请 ...

  6. c++11 动态内存管理-分配器 (std::allocator)

    定义于头文件 <memory> template< class T >struct allocator; (1) template<>struct allocato ...

  7. CoreCLR源码探索(三) GC内存分配器的内部实现

    在前一篇中我讲解了new是怎么工作的, 但是却一笔跳过了内存分配相关的部分. 在这一篇中我将详细讲解GC内存分配器的内部实现. 在看这一篇之前请必须先看完微软BOTR文档中的"Garbage ...

  8. stl源码剖析_STL源码剖析 阅读笔记(二)allocator

    一.空间分配器 allocator 从使用上看,空间分配在任何语言的任何组件都不需要我们去过多关心,因为语言.组件的底层肯定都比较完整的做了这件事情. 从实现上看,学习 allocator 的原理在源 ...

  9. 内存分配器ptmalloc,jemalloc,tcmalloc调研与对比

    内存分配器ptmalloc,jemalloc,tcmalloc调研与对比 rtoax 2020年12月 1. 概述 内存管理不外乎三个层面,用户程序层,C运行时库层,内核层.allocator 正是值 ...

  10. ptmalloc、tcmalloc与jemalloc内存分配器对比分析

    目录 背景介绍 ptmalloc 系统向看ptmalloc内存管理 用户向看ptmalloc内存管理 线程中内存管理 Chunk说明 tcmalloc 系统向看tcmalloc内存管理 用户向看tcm ...

最新文章

  1. R语言使用lm构建线性回归模型、并将目标变量对数化实战:可视化模型预测输出与实际值对比图、可视化模型的残差、模型系数(coefficient)、模型总结信息(summary)、残差总结信息
  2. 文件上传 java web_JavaWeb 文件上传下载
  3. apache 服务器概述--安装(一)
  4. Android: 自定义Tab样式
  5. 在.NET Core中使用MongoDB明细教程(3):Skip, Sort, Limit, Projections
  6. mysql 查询列表是否关注_点赞功能,用mysql还是redis?
  7. python中提取pdf文件某些页_付费?是不可能的!处理 PDF 只需几行代码,彻底解放双手!...
  8. Sentinel降级_异常比例_分布式系统集群限流_线程数隔离_削峰填谷_流量控制_速率控制_服务熔断_服务降级---微服务升级_SpringCloud Alibaba工作笔记0040
  9. PAT 1079 Total Sales of Supply Chain[比较]
  10. php接口和多态的概念以及简单应用
  11. java开发sublime插件_开发者最常用的8款Sublime text 3插件
  12. Stm32F4XX开启FPU浮点运算
  13. 【ESP 保姆级教程】疯狂传感器篇 —— 案例:ESP8266 + MQ3酒精传感器 + webserver(局域网内曲线变化图)+ 自定义飞书告警
  14. 替换空格 ——《剑指offer》
  15. web ui 套件_复古UI套件
  16. 头歌 CC++基本输入输出
  17. 当display:flex弹性布局与position:absolute/fixed定位一起用,会出现的问题与解决方法
  18. CAD画图教程,手把手教你画“鱼”
  19. 浅析计算机信息管理系统任务书,计算机工程系学生就业信息管理系统任务书
  20. 实验五 大学数据库系统中,使用游标编写存储过程,输入学号查询成绩

热门文章

  1. 解决Mac无法睡眠问题
  2. 告别手敲template,自动生成基础模板(Vue)
  3. matlab tfrstft工具箱,toolbox matlab时间序列分析工具箱 - 下载 - 搜珍网
  4. SpringBoot 查询今年所有节假日
  5. Matlab闪退打不开解决方法
  6. 10000个怎么用js写 创建li_JavaScript给每一个li节点绑定点击事件的实现方法
  7. exchange服务器磁盘性能,Exchange磁盘空间不够了怎么办?
  8. php中划弧线,cad画弧形的快捷键是什么?如何画弧形?
  9. LSTM(Long Short Term Memory)和RNN(Recurrent)教程收集 (知乎)
  10. 用c#实现简单的登录和注册功能