STL 源码剖析 空间配置器

  • 以STL的运用角度而言,空间配置器是最不需要介绍的东西,它总是隐藏在一切组件(更具体地说是指容器,container) 的背后
  • 但是STL的操作对象都存放在容器的内部,容器离不开内存空间的分配
  • 为什么不说allocator是内存配置器而说它是空间配置器呢?因为空间不一定 是内存,空间也可以是磁盘或其它辅助存储介质。是的,你可以写一个allocator, 直接向硬盘取空间。以下介绍的是SGI STL提供的配置器,配置的对象,这里分配的空间是指分配内存

 

  •  set_new_handler()总结
namespace JJ
{template <class T>inline T* _allocate(std::ptrdiff_t size,T*){std::set_new_handler(0);T* tmp = (T*)(::operator new ((std::size_t)(size * sizeof(T))));if (tmp == 0){std::cerr << "out of memory " << std::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 _destroy(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;//rebind allocator of type Utemplate <class U>struct rebind{typedef allocator<U> other;};//hint used for locality. ref.[Austern],pl89pointer 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 destroy(pointer p){_destroy(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 STL在这个项目上根本就逸脱了 STL标准规格,使用一个专属的、拥有次层配置(sub-allocation)能力的、效率优越的特殊配置器,稍后有详细介绍
  •  备 次 配 置 力 (sub-allocation)的 S G I 空间配置器
  • SGI STL的配置器与众不同,也与标准规范不同,其名称是a llo c 而非 allo ca to r,而且不接受任何参数。换句话说,如果你要在程序中明白采用SGI配 置器,则不能采用标准写法:

  •  SGI STL allocator未能符合标准规格,这个事实通常不会给我们带来困扰,因 为通常我们使用缺省的空间配置器,很少需要自行指定配置器名称,而SGI STL的每一个容器都已经指定其缺省的空间配置器为a llo c .例如下面的vector声明:

  •  S G I标 准 的 空 间 配 量 器 ,std::allocator
  • SG I特 殊 的 空 间 配 置 器 ,std::alloc
  • 上一节所说的a llo c a to r 只是基层内存配置/释放行为(也就是 ::operator new和 : :operator delete)的一层薄薄的包装,并没有考虑到任何效率上的强 化. S G I另有法宝供其本身内部使用。

  •  这其中的new算式内含两阶段操作3: (1 )调 用 ::operator new配置内存; ⑵ 调 用Foo::Foo()构造对象内容。delete算式也内含两阶段操作:(1)调用Foo: :-Foo ()将对象析构;(2 ) 调 用 ::operator delete释放内存。
  • 为了精密分工,STL a llo c a to r决定将这两阶段操作区分开来。内存配置操作 由 alloc: al locate ()负责,内存释放操作由alloc : : deallocate ()负责;对象构造操作由::construct:()负责,对象析构操作由:;destroy负责

  •  内存空间的配置/释放与对象内容的构造/析构,分别着落在这两个文件身上。其 中 <stl_construct .h>定义有两个基本函数:构造用的 construct() 和析构用的destroy。。在一头栽进复杂的内存动态配置与释放之前,让我们先看清楚这 两个函数如何完成对象的构造和析构。

  •  构造和析构基本工具:co n stru ct()和 destroy()

  •  这两个作为构造、析构之用的函数被设计为全局函数,符合STL的规范.此外,STL还规定配置器必须拥有名为construct ()和 destroy ()的两个成员函数(见2.1节 ),然而真正在SGI STL中大显身手的那个名为std::alloc的配 置器并未遵守这一规则(稍后可见
  • 上 述 construct ()接受一个指针p 和一个初值value,该函数的用途就是 将初值设定到指针所指的空间上。C++的 placement new 运算子5 可用来完成这一任务。
  • destroy() 有两个版本,第一版本接受一个指针,准备将该指针所指之物析 构掉。这很简单,直接调用该对象的析构函数即可。第二版本接受first和 last 两个迭代器(所谓迭代器,第3 章有详细介绍),准备将 [first, last)范围内的所有对象析构掉。我们不知道这个范围有多大,万一很大,而每个对象的析构函数都无关痛痒(所谓rrzvia/destructor), 那么一次次调用这些无关痛痒的析构函数, 对效率是一种伤害。
  • 因此,这里首先利用value_type()获得迭代器所指对象的 型别,再利用 _type_traits< T> 判断该型别的析构函数是否无关痛痒.若是 (一true_type), 则什么也不做就结束;若 否 (— false_type), 这才以循环 方式巡访整个范围, 并在循环中每经历一个对象就调用第一个版本的destroy ()

空 间 的 配 置 与 释 放 , std::alloc

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

  •  C + + 的 内 存 配 置 基 本 操 作 是 ::operator new ( ) , 内存释放基本操作 是 : operator delete)). 这两个全局函数相当于C 的malloc ( ) 和 f r e e O 函 数。是的,正是如此,S G I 正是以malloc ()和 f r e e O 完成内存的配置与释放。 
  • 考虑到小型区块所可能造成的内存破碎问题,S G I 设计了双层级配置器,第一级配置器直接使用 malloc() 和 fme(),第二级配置器则视情况采用不同的策略:当配置区块超过128 bytes时,视 之 为 “足够大”,便调用第一级配置器;当配 置区块小于128 bytes时,视 之 为 “过小”,为了降低额外负担(overhead,见 2.2.6 节 ),便采用复杂的memory pool整理方式,而不再求助于第一级配置器, 整个设 计究竟只开放第一级配置器,或是同时开放第二级配置器,取决于_ use_malloc是否被定义(唔,我们可以轻易测试出来,SGI STL并未定义一_USE_MALLOC)

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

  •  其内部四个成员函数其实都是单纯的转调用,调用传递给配置器(可能是第一级也可能是第二级)的成员函数.这个接口使配置器的配置单位从bytes转为个别元素的大小(sizeof (T) ) . SGI STL容器全都使用这个simple_alloc接口,例如:

 第 一 级 配 置 器 ―malloc_alloc_template 剖析

  •  第一级配置器以malloc () , free () , realloc ()等 C 函数执行实际的内存配置、释放、重配置操作,并实现出类似C + + new-hand宜了的机制。是的,它不能 直 接 运 用 C++ new-handier机制,因为它并非使用::operator n e w 来配置内存。 
  • 所 谓 C++ new handler机制是,你可以要求系统在内存配置需求无法被满足 时 ,调用一个你所指定的函数。换句话说,一 旦 ::operator new无法完成任务, 在 丢 出 std::bad_alloc异常状态之前,会先调用由客端指定的处理例程。该处理例程通常即被称为new-handiero new-handier解决内存不足的做法有特定的模式,请 参 考 《婀 伽 e C++》2 e 条款7 
  • handler

  •  程通常即被称为new-handiero new-handier解决内存不足的做法有特定的模式
  • 注意,S G I 以 malloc而 非 ::operator new来配置内存(我所能够想象的 一个原因是历史因素,另一个原因是C + + 并未提供相应于 realloc () 的内存配 置 操 作 ),因此,S G I 不能直接使用C + + 的 set_new_handler (),必须仿真一个类似的 set_malloc_handler () 
  • 请 注 意 ,S G I 第 一 级 配 置 器 的 allocate ()和 realloc ( ) 都是在调用 malloc ()和 realloc ()不成功后,改调用 oom_malloc ()和 oom_realloc ()=后两者都有内循环,不 断 调 用 “内存不足处理例程”,期望在某次调用之后,获得足够的内存而圆满完成任务。但 如 果 “内存不足处理例程”并未被客端设定,oom_malloc() 和 oom_realloc() 便老实不客气地调用 — THROW_BAD_ALLOC,丢 出 bad_alloc异常信息,或 利 用 exit(l)硬生生中止程序。 
  • 记住,设 计 “内存不足处理例程”是客端的责任,设 定 “内存不足处理例程”也是客端的责任.再一次提醒你,“内存不足处理例程”解决问题的做法有着特定的模式,请 参 考 [Meyers98]条款7

第二级配置器 __default_alloc_template 剖析

  • 第二级配置器多了一些机制,避免太多小额区块造成内存的碎片。小额区块带来的其实不仅是内存碎片,配置时的额外负担(overhead)也是一个大问题% 额外 负担永远无法避免,毕竟系统要靠这多出来的空间来管理内存,如图2-3所示。但是区块愈小,额外负担所占的比例就愈大,愈显得浪费。

  • SGI第二级配置器的做法是,如果区块够大,超过128 bytes时,就移交第一 级配置器处理.当区块小于128 bytes时,则以内存池(memory pool)管理,此法 又称为次层配置(sub-allocation):
  • 每次配置一大块内存,并维护对应之自由链表 {free-list). 下次若再有相同大小的内存需求,就直接从free-lists中拨出。如果客户端释还小额区块,就由配置器回收到free-lists中—— 是的,别忘了,配置器除了负 责配置,也负责回收。
  • 为了方便管理,SGI第二级配置器会主动将任何小额区块的内存需求量上调至8 的倍数(例如客端要求30 bytes,就自动调整为32 bytes),并维护 16 个free-lists,各自管理大小分别为 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128 bytes的小额区块。free-lists的节点结构如下:

  • 诸君或许会想,为了维护链表(lists), 每个节点需要额外的指针(指向下一 个节点),这不又造成另一种额外负担吗?你的顾虑是对的,但早已有好的解决办法。
  • 注意’上 述 obj所用的是union,由于union之故,从其第一字段观之, obj可被视为一个指针,指向相同形式的另一个。切从其第二字段观之。obj可 被视为一个指针,指向实际区块,如图2-4所示。一物二用的结果是,不会为了维护链表所必须的指针而造成内存的另一种浪费(我们正在努力节省内存的开销呢)。这种技巧在强型(strongly typed)语言如Java中行不通,但是在非强型语言如C++中十分普遍

 空 间 配 置 函 数 allocate()

  • 此函数首先判断区块大小,大 于 128 bytes就调用第一级配置器,小 于 128 bytes就检查对应的free listo 如 果 free list之内有可用的区块,就直接拿来 用,如果没有可用区块,就将区块大小上调至8 倍数边界,然后调用refilio, 准备为free list重新填充空间。refill ()将于稍后介绍。

 

  •  free_list维护8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128 bytes的小额区块,需要分配内存的时候,如上图所示,需要分配的大小是96,my_free_list先找到96对应的链条,result指向想要的96区块,my_free_list移动到下一个区块,将result需要的区块排除到链外,表示为其分配了区间

空 间 释 放 函 数 deallocate。

  • 身为一个配置器, _ default_alloc_template 拥有配置器标准接口函数deallocated o 该函数首先判断区块大小,大于128 bytes就调用第一级配置器, 小于128 bytes就找出对应的free list,将区块回收。

  •  使用q指向需要回收的空间,使用my_free_list找到与之大小相匹配的存储区块的链条,使用q衔接对应的 存储区块的链条位置,移动初始位置

重 新 填 充 free lists

  • 回头讨论先前说过的allocate (). 当它发现free list中没有可用区块了时, 就 调 用 refillO,准备为free list重新填充空间.新的空间将取自内存池(经由 chunk_alloC ()完 成 )。缺省取得2 0 个新节点(新区块),但万一内存池空间不 足,获得的节点数(区块数)可能小于20:

 内 存 池 ( m e m o r y pool )

  • 从内存池中取空间给斤e。 使用,是 chunk_alloc()的工作: 

  • 上图存在一个错误,size是需要的大小,nobjs是需要的数量

 

  •  上述的chunk_alloc ( ) 函数以end_free - s t a r t _ f r e e 来判断内存池的水量。如果水量充足,就直接调出20个区块返回给free list。如果水量不足以提供20 个区块,但还足够供应一个以上的区块,就拨出这不足20个区块的空间出去。这时候其pass by reference的n o b js参数将被修改为实际能够供应的区块数。如果 内存池连一个区块空间都无法供应,对客端显然无法交待,此时便需利用m alloc 从 heap中配置内存,为内存池注入活水源头以应付需求。新水量的大小为需求量 的两倍,再加上一个随着配置次数增加而愈来愈大的附加量。

  •  如果一开始free_list 和 内存池都是空的,当用户需求数据的时候,发现没有内存,申请的空间是20的两倍,也就是40,一半内存交给free_list用于数据的维护,一半数据给 内存池;当再次申请内存时,所对应的free_list不存在数据,就会先向内存池索要内存,满足一部分之后,将剩余的内存交给 free_list;

 内存基本处理工具

  • STL定义有五个全局函数,作用于未初始化空间上• 这样的功能对于容器的实现很有帮助,我们会在第4章容器实现代码中,看到它们肩负的重任 前两个函数是2.2.3节说过的、用于构造的construct ()和用于析构的destroy (),另三个函数uninitialized_copy(),uninitialized_fill(),uninitialized_fill_n(), 分别对应于高层次函数copy () fill () fill_n() 这些都是STL算法,将在第6 章介绍。如果你要使用本节的三个低层次函数,应该包含 <memory>, 不过SG I把它们实际定义于 <stl_uninitialized>。

uninitialized_copy

  •  uninitialized_copy ()使我们能够将内存的配置与对象的构造行为分离开来。如果作为输出目的地的[result/ result+(last-first))范围内的每一个迭代器都指向未初始化区域,则 uninitialized_copy ()会使用copy constructor,给身为输入来源之[first, last)范围内的每一个对象产生一份复制品’放进输出 范围中。换句话说,针对输入范围内的每一个迭代器该函数会调用
    construct (&* (result+ (i-f irst) ) , *i), 产 生 * i 的复制品,放置于输出范围的相对位置上。式中的construct ()已于2.2.3节讨论过。
  • result 指向内存拷贝的目的地址,i和first指向的是同一个内存区间,使用i和first之间的差值,将*i(元素的数值)拷贝到指定的位置
  • 果你需要实现一个容器,uninitialized.copy() 这样的函数会为你带来 很大的帮助,因为容器的全区间构造函数(range constructor) 通常以两个步骤完 成:

  •  C++ 标准规格书要求 uninitialized_copy () 具 有 'commit or rollback 语 意,意思是要么“构造出所有必要元素”,要么(当有任何一个copy constructor失 败时)“不构造任何东西””

2.3.2 u n in itia liz e d _fill

  •  &*i   其实就是元素的位置,*i代表元素的数值,加上&,就是取地址,后面接入x,construct就是在指定的位置上填写x
  • 与 uninitialized_copy() 一样,uninitialized_f ill ()必须具备 acommit or rollback语意,换句话说,它要么产生出所有必要元素,要么不产生任何元素。如果有任何一个 copy constructor 丢出异常(exception) ,uninitialized_f ill (),必须能够将已产生的所有元素析构掉。

u n in itia liz e d _ fill_ n

  •  uninitialized_fill_n ()能够使我们将内存配置与对象构造行为分离开来。它会为指定范围内的所有元素设定相同的初值。

  • fill_n
  • 如果是POD类型,使用_true_type,交由高阶函数执行,使用fill_n对每个元素进行赋值
  • 如果不是POD类型,使用_flase_type,调用construct函数对每个元素进行赋值

 uninitialized_copy

  •  这个函数的进行逻辑是,首先萃取出迭代器result的 value type (详见第3 章 ) , 然后判断该型别是否为PO D 型别:

  •  如果是POD类型,采用最有效率的办法就是复制的方式,内部调用fill_n对元素进行复制
  • 如果不是POD类型,只能使用最保险安全的construct的函数构造的方式

  • char是一个字节
  • wchar_t  使用sizeof判断不同环境下的占据的单个元素的空间大小
  • 使用memmove 直接对指定的内存区间进行数据的移动 

u n in itia liz e d _ fill

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/446418.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

中科大 计算机网络7 分组延迟 分组丢失 吞吐量

分组丢失和延迟的原因 队列太长没有意义&#xff0c;用户需求 排队&#xff1a;输出能力<到来的分组&#xff0c;需要等待 四种分组延迟 节点处理延迟&#xff1a;确定的 排队延迟&#xff1a;随机&#xff0c;取决于网络情况 一个比特的传输时间&#xff1a; R1Mbps …

STL源码剖析 迭代器iterator的概念 和 traits编程技法

iterator模式定义如下&#xff1a;提供一种方法&#xff0c;使之能够依序巡访某个 聚合物(容器)所含的各个元素&#xff0c;而又无需暴露该聚合物的内部表述方式.STL的中心思想在于&#xff1a;将数据容器(containers)和算法(algorithms)分开&#xff0c;彼此独立设计&#xff…

中科大 计算机网络11 应用层原理

应用层大纲 传输层向应用层提供的服务&#xff0c;形式是Socket API&#xff08;原语&#xff09; 一些网络应用的例子 互联网层次中&#xff0c;应用层协议最多 流媒体应用&#xff1a;直播 网络核心最高的层次就是网络层 应用进程通信方式 C/S&#xff1a; 客户端&…

STL源码剖析 序列式容器 vector 和 ilist

Vector list 单向链表 ilistlist的删除操作&#xff0c;也只有指向被删除元素的迭代器会失效&#xff0c;其他迭代器不会受到影响

中科大 计算机网络5 接入网和物理媒体

接入网 接入网&#xff1a;把边缘&#xff08;主机&#xff09;接入核心&#xff08;路由器&#xff0c;交换机&#xff09; 骨干网【连接主机和主机】和接入网中都有物理媒体 接入方式&#xff1a;有线和无线 带宽共享/独享 接入网&#xff1a;住宅接入modem modem调制解调…

STL源码剖析 序列式容器 deque双端队列

相较于vector的内存拷贝&#xff0c;deque在内存不足时只需要进行内存的拼接操作即可&#xff0c;不需要重新配置、复制、释放等操作&#xff0c;代价就是迭代器的架构不是一个普通的指针&#xff0c;比较复杂d e q u e 的迭代器 deque是分段连续空间。维持其“整体连续”假象…

中科大 计算机网络6 Internet结构和ISP

互联网的结构 端系统通过接入ISPs接入互联网 n个ISP互相连接&#xff1a; IXP,Internet exchage point:互联网接入点&#xff0c;互联网交互点 ISP&#xff1a;互联网服务提供商&#xff0c;提供接入&#xff0c;提供网络【中国移动&#xff0c;中国电信】 ICP&#xff1a…

STL源码剖析 Stack栈 queue队列

随机迭代器用于随机数据访问&#xff0c;所以栈stack不具备此功能

中科大 计算机网络8 协议层次和服务模型

协议层次 协议层次&#xff1a;现实生活中的例子 分层 分层处理和实现复杂系统 图中&#xff0c;左边是模块&#xff0c;右边是分层 计算机的设计是分层&#xff0c;每一层实现一个或一组功能&#xff0c;下层向上层提供服务&#xff1b;但效率比较低 对等层实体通过协议来交换…

STL源码剖析 heap堆结构

heap一般特指max-heap&#xff0c;即最大的元素位于heap和array的首部 heap不提供遍历功能&#xff0c;也不提供迭代功能

中科大 计算机网络9 互联网历史

总纲 计算机网络 早期1960以前 1961-1972 NCP协议&#xff1a;相当于现在的TCP和IP协议 每个节点即是数据的源也是数据的目标

STL源码剖析 序列式容器 slist

STL l i s t 是个双向链表(double linked lis t) 。SGI STL提供了一个单向链 表 (single linked lis t) , 名 为 slist s l i s t 和 l i s t 的主要差别在于&#xff0c;前者的迭代器属于单向的Forwardlterotor, 后者的迭代器属于双向的Bidirectional Iterator.为此&#xff0…

中科大 计算机网络12 Web和HTTP

Web与HTTP 对象&#xff1a;web页中其实是对象链接 URL&#xff1a;通用资源定位符【任何对象都可以使用URL来唯一标识】 用户名&#xff1a;口令【支持匿名访问&#xff0c;用户名和口令不计】 端口&#xff1a;HTTP&#xff1a;80 FTP&#xff1a;21【使用默认端口号&#x…

STL源码剖析 关联式容器 树 红黑树、二叉搜索树、平衡二叉搜索树

所谓关联式容器&#xff0c;观念上类似关联式数据库(实际上则简单许多)&#xff1a;每笔数据(每个元素)都有一个键值(key)和一个实值(value) 2。当元素被插入到关联式 容器中时&#xff0c;容器内部结构(可能是RB-tree,也可能是hash-table)便依照其键 值大小&#xff0c;以某种…

北京大学 软件工程1 软件 软件工程 软件开发 软件工程框架

软件的定义 重新定义软件 新一代信息技术 区块链 创造性思维 软件的特点 软件的种类 支撑软件&#xff1a;VC&#xff0c;PyCharm等 应用软件&#xff1a;QQ&#xff0c;微信 软件工程的起源 软件开发的三个阶段 软件工程概念的提出 软件工程的定义 软件工程将系统化&#…

java学习_Python基础学习教程:从0学爬虫?让爬虫满足你的好奇心

Python基础学习教程&#xff1a;从0学爬虫&#xff1f;让爬虫满足你的好奇心有必要学爬虫吗&#xff1f;我想&#xff0c;这已经是一个不需要讨论的问题了。爬虫&#xff0c;“有用”也“有趣”&#xff01;这个数据为王的时代&#xff0c;我们要从这个庞大的互联网中来获取到我…

安卓rom制作教程_安卓手机TWRP_Recovery卡刷图文教程 适用于卡刷ROM,TWRP救砖

扫一扫二维码&#xff0c;关注我&#xff0c;解决刷机各种疑难杂症 ROM乐园独家支持最近有很多小伙伴问怎么去卡刷&#xff0c;卡刷的操作是什么&#xff0c;什么是卡刷&#xff0c;小编就仔细来写一下卡刷教程吧&#xff0c;记住&#xff0c;我们所说的卡刷&#xff0c;并不是…

东软 软件工程1 软件危机 软件工程 软件生命周期

软件危机 软件危机产生的原因 消除软件危机的途径&#xff1a; 软件工程历史 软件工程的概念 软件工程项目的基本目标 软件工程的基本原理 软件生命周期 软件工程的中的软件生命周期