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

一、空间分配器 allocator

使用上看,空间分配在任何语言的任何组件都不需要我们去过多关心,因为语言、组件的底层肯定都比较完整的做了这件事情。

实现上看,学习 allocator 的原理在源码学习中是首当其冲。因为没有空间分配,则无从谈起对象创建。这里说是空间分配,而不是内存分配,是因为也可以在内存之外的地方(如硬盘)分配空间。

分配器主要作用就是分配空间,根据规范,其需要实现一些接口,完成一些关于空间分配的功能。标准接口规范见附录(一)。

本文会提到以下几个方面:

  • SGI STL 分配器介绍
  • construct 和 destroy
    • destroy 接收指针和迭代器的方法
  • alloc 分配器
    • 一层分配器
    • 二层分配器 和 free-list
      • 分配
      • 释放
      • 补充
  • 全局函数
    • uninitialized_copy
    • uninitialized_fill
    • uninitialized_fill_n

二、SGI STL 的 alloc

SGI STL 的分配器与众不同,也与标准规范不同,其名称是 alloc 而非 allocator,而且不接受任何参数。具体来说,想在程序中明确使用 SGI 分配器,不能写std::allocator<int>,而要写成std::alloc

即使它不符合标准规范,也不会对我们使用造成任何影响,因为通常我们都使用缺省的分配器,而不会自己指定。而 STL 的每一个容器都指定缺省分配器为 alloc。

当然了,SGI 也定义了符合部分标准、名为 allocator 的分配器,但出于其效率原因,STL 从未使用它,也不推荐程序员使用。它只是把 ::operator new 和 ::operator delete 做了一层薄薄的封装,没有做优化。详细代码见附录(二)。

下面详细聊聊 SGI STL 实现的 alloc 。

一般而言,C++的内存分配和释放操作如下:

class Foo {...};
Foo * pf = new Foo;    // 分配内存,构造对象
delete pf;             // 析构对象,释放内存
  • new 内含两步操作:(1)调用 ::operator new 分配内存(2)调用 Foo::Foo() 构造对象内容。
  • delete 内含两步操作:(1)调用 Foo::~Foo() 析构对象(2)调用 ::operator delete 释放内存。

为了精细分工,分配器将这两个步骤分开做。内存分配由 alloc::allocate() 负责,内存释放由 alloc::deallocate() 负责;对象构造由 ::construct() 负责,对象析构由 ::destroy() 负责。

// STL规定分配器 allocator 定义于 memory 中
#include <memory>// memory 中含有两个文件
#include <stl_alloc.h>          // 负责内存空间的分配和释放,定义了 一级、二级分配器。
#include <stl_construct.h>      // 负责对象内容的构造和析构,有 construct 和 destroy 方法。// memory 中还有一个文件
#include <stl_uninitialized.h>  // 定义了一些全局函数,用来填充fill 或者 复制copy 大块内存的数据
// 其中有如下方法
// un_initialized_copy()
// un_initialized_fill()
// un_initialized_fill_n()
// 这些方法不属于分配器的范畴,但与对象初值设置有关,对大规模元素初值设置很有帮助。
// 在效率上,最差会调用 construct,最佳会调用C的 memmove 进行内存移动。

(一)construct 和 destroy

对于对象的 construct 和 destroy 可以概括如下图所示。其源码见附录(三)

c7965434b7922b13d3f6863c263b4952.png
  • construct 接收 指针p 和 初值value,会将value设置到p所指的空间上。
  • destroy 可以接收 指针、迭代器。
    • 基本类型指针:不做处理
    • 对象类型指针:调用析构函数
    • 迭代器:会判断析构函数是否为 trivial destructor(无用的、没必要的、无意义的析构函数)
      • 是:则不做处理
      • 否:调用 迭代器中每个元素的析构函数。

这里有个问题是,如何判断是否为 trivial呢?

答案是:使用 __type_traits<T>::has_trivial_destructor() ,该函数会返回 __true_type 或 __false_type,前者代表是trivial,后者代表是有意义的。

该类的具体实现需要去研究下 traits,这里先不展开。

(二)STL alloc

<stl_alloc.h> 负责了对象构造前的空间分配和对象析构前的空间释放,有下面几个设计原则:

  • 向 system heap 申请空间
  • 考虑多线程状态(为了将问题简化,这里不讨论多线程状态)
  • 考虑内存不足的应变措施
  • 考虑过多小块内存造成的碎片问题

C++ 的内存分配和释放主要使用 ::operator new() 和 ::operator delete(),这两个相当于C的 malloc() 和 free(),SGI 正是以 malloc 和 free 完成的内存分配和释放。

SGI 设计了双层分配器,如下图所示:

  • 第一级直接使用 malloc 和 free
  • 第二级,当需求大于128 bytes 时,调用一级分配器;小于等于128 bytes 则调用二级分配器。

71899b64a470f818ba52a3107086faef.png

具体采用哪种分配器,需要看 __USE_MALLOC 是否被定义。定义了则用一级分配器,否则调用二级分配器。

SGI 为 alloc 提供了一个 simple_alloc 的接口封装,使得外层使用时无需考虑内部具体用的一级还是二级。SGI STL 的容器都使用这个 simple_alloc 接口,而非直接使用 alloc。代码见附录(四)。

c266badca469f9d203401b766b32b490.png

一级分配器的原理比较简单,正常情况就是调用 malloc 和 free 做分配和释放。当内存不够时需要使用 oom_malloc,在该函数中,会循环调用一个 handler 来处理内存不足的情况。这个 handler 是需要自己指定的,如果没有指定,则抛出 std::bad_alloc 异常。这个 handler 一般称为 new-handler,在 《Effective C++》2e item7 中有特定的解决模式。

(三)STL 二级分配器

下面着重说说二级分配器

二级分配器可以避免产生过多的小区块,可以解决内存碎片和过多的额外开销(系统需要多出来的空间管理内存,可以说是给系统“交税”)。

二级分配器以内存池(memory pool)管理小于128 bytes 的内存,称为次层分配(sub-allocation):先分配一大块内存,组成一个自由链表(free-list),每次要取一定量内存时,从 free-list 中取;在用完后,分配器就归还给 free-list。

分配器会维护 空间为 8、16、24、……、128 这16个 free-list,在分配小内存时,会向上取整(Round Up),寻找最近的 free-list。

free-list 节点结构是一个联合体,该节点在free-list中时,内容是一个指向 下一个节点的指针,在客户端使用时,是具体的数据。这样一物二用,不会造成维护链表指针的内存浪费。这个技巧在强类型语言(Strong Typed)中如 Java 行不通,但在弱类型语言(Weak Typed)中如 C++十分常见。

union obj{union obj * free_list_link;  char client_data[1];         // client use
}

854f117a8d29d715e054706e633ee5b7.png
free-list 的实现技巧

次层分配中从 free list 分出内存的步骤 allocate 如下图所示:

e0e9850240c93bf637f3d6ae62f55943.png

次层分配中释放内存,往 free list 中归还的步骤 deallocate 如下图所示:

aa1428915638b71c97e92680a2cd766d.png

当 free-list 的空间用尽后,会触发 refill 操作,重新给 free-list 补充 20个节点。refill 会调用 chunk_alloc,该函数中会做具体从内存池中取内存的操作。其过程如下所示。

bee99984612e7cb4c62d22e65806749a.png

ca394099fd3c8dc0ab95ec6800e0e88c.png

简而言之就是,先找自己(32找32),再找亲友(64找32),实在不行就求助大家(96找32)。

三、内存基本处理工具

STL 定义了五个全局函数,除了前文提到的 construct 和 destroy,还有3个用来处理大块内存的复制和移动的 unitialized_copy、uninitialized_fill、uninitialized_fill_n 分别对应高层次的函数 copy、fill、fill_n。

unitialized_copy 函数让内存配置与对象构造行为分开。如果目标地址指向的空间都是未初始化区域,则会直接把源区域的对象产生复制品直接放到目标地址。STL 规范中要求该函数具有原子性,要么全部构造出来,要么全部不构造。

uninitialized_fill、uninitialized_fill_n 也和 unitialized_copy 类似。

这三个函数都会判断 对象是否为 POD(Plain Old Data,标量 or 传统 C 结构体),POD 会具有 trivial 函数,如果是 POD 则用最有效率的方法,如果非 POD 则用最安全的方法。过程大致如下所示。

414b754cec9f1ebfe379c685bb6da155.png

附录

(一)标准接口规范

根据 STL 规范,allocator 必须要实现以下接口。

44b1b92886d247befdf6e4146ff27a5a.png

7ce3e770e343a8107171de0096c7df0e.png

(二)SGI allocator 源码

下面是 SGI 实现的 allocator 全貌

1297eaddcb57129121adca50e32917af.png

a3200ef8ae651c1d17f42ff61719d33a.png

e4d6bf1cc2df554e70675b24b1f55929.png

(三)construct 和 destroy 源码

188b479b4989f933034bca40e113f9a1.png

7019d5aa232f19369cef04a60e8701ad.png

(四)simple_alloc 和 vector

1bb90c64abf4fdb2f28a1f1d76d615b6.png

f8c8b7b4a7fdeea1498f154d9da9e879.png

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

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

相关文章

easyexcel将对象处理为多列,自增序列

概述 主要记录在开发中遇到的问题&#xff1a;使用easyexcel导出excel&#xff0c;一般数据都是保存在数据库中&#xff0c;如果查询返回的是一个实体类&#xff0c;且里面有嵌套的实体类对象&#xff0c;这时导出的时候要先对查询出的数据进行业务逻辑处理&#xff0c;让它符…

python做excel表格代码_python操作excel表格

我们在写测试用例的时候&#xff0c;是创建一个表格.xlsx&#xff0c;然后把各种条件加到这张表格中去&#xff0c;所以&#xff0c;如何对excel表格操作&#xff0c;是相当重要的一环&#xff0c;那么&#xff0c;接下来&#xff0c;这篇博客就直接教会大家如何通过python去处…

Windows 查看程序ip地址(面对小白)

前言&#xff1a;Windows自带资源管理器可以查看程序的IP地址。下面以微信通话为例&#xff0c;详细步骤如下(面向小白)&#xff1a; 打开任务管理器 打开方法(以下方法都可以)&#xff1a; 键盘按住 【ctrl】 【alt】 【delete】 ,选择【任务管理器】Windows 10 以下系统…

【转】C#中相同不同程序集存在相同的命名空间的时候的冲突解决办法

快速解决办法描述描述: 1.将相同命名空间的不同程序集分别进行取别名&#xff1a;【具体操作:右击相同程序的引用&#xff0c;在别名上修改&#xff0c;默认的为global】。 2.调用&#xff1a;在所在调用文件里面最前面写 extern alias 别名&#xff0c;然后using 别名.Names…

chrome webdriver_(最新版)如何正确移除Selenium中的 window.navigator.webdriver

摄影&#xff1a;产品经理产品经理的三文鱼炒饭在《一日一技&#xff1a;如何正确移除Selenium中window.navigator.webdriver的值》一文中&#xff0c;我们介绍了在当时能够正确从Selenium启动的Chrome浏览器中移除window.navigator.webdriver的方法。后来时过境迁&#xff0c;…

【转】2.3async中必须始终返回Task(@Ron.liang)

Asp.Net Core 轻松学-经常使用异步的你,可能需要看看这个文章 目录 前言1. 异常的发生来得太突然2. 问题所在3. 问题的解决方案前言 事情的起因是由于一段简单的数据库连接代码引起&#xff0c;这段代码从语法上看&#xff0c;是没有任何问题&#xff1b;但是就是莫名其妙的报…

php隐藏webshell_PHP 安全的十个必备技巧

在这篇文章中&#xff0c;我将尝试为你提供一些可以提高 PHP 应用程序安全性的具体步骤。我关注的是 PHP 配置本身&#xff0c;所以我们不会讨论 SQL 注入、HTTPS 或其他与 PHP 无关的问题。我将使用我的 docker-entrypoint.sh 脚本中的 bash 行来说明示例&#xff0c;但当然你…

aop实现原理_Java:由浅入深揭开 AOP 实现原理

点击上方“Java专栏”&#xff0c;选择“置顶或者星标”第一时间阅读精彩文章&#xff01;1、☞ 程序员进阶必备资源免费送「21种技术方向&#xff01;」 点击查看☜2、☞ 《Java面试手册》.PDF 点击查看作者&#xff1a;马佩juejin.im/post/5bf4fc84f265da611b57f906概述&a…

【转】C#进阶系列——WebApi 接口参数不再困惑:传参详解

阅读目录 一、get请求 1、基础类型参数2、实体作为参数3、数组作为参数4、“怪异”的get请求二、post请求 1、基础类型参数2、实体作为参数3、数组作为参数4、后台发送请求参数的传递三、put请求 1、基础类型参数2、实体作为参数3、数组作为参数四、delete请求五、总结正文 前…

【转】01Teams的前世今生

说到Teams&#xff0c;这到底是一个什么产品&#xff1f;有人说它是团队协作工具&#xff0c;有人说它是云视频系统&#xff0c;有人说它是Hub&#xff0c;还有人说它是微软有史以来发展最快的一个产品&#xff0c;还有人说它完全是一个高效办公神器。其实都是对的。 Teams集成…

oracle insert 当前时间_Oracle知识点总结

目录1、Oracle的安装与下载2、基础查询3、条件查询 1)对比运算符 2)逻辑运算符 3)模糊查询 4)排序4、函数 1)数值函数 2)字符函数 3)时间日期函数 4)转换函数 5)通用函数5、聚合函数6、分组查询在学习Oracle之前&#xff0c;需要先有一定的数据库基…

【转】C#各类控件的输入输出(思维导图、知识点分析、案例解析)

第六周学习笔记—C#各类控件的输入输出 1.思维导图 知识点汇总&#xff1a; 着重介绍几个常用控件&#xff1a; 数据显示控件 DataGridView控件 列设置 a)、列的宽度铺满这个控件 设置如下&#xff1a;把AutoSizeColumnsModeFill;//可以在属性窗口中设置 b)、列名居中 代…

sqlserver连接字符串_10分钟使用EF Core连接MSSQL数据库

(给DotNet加星标&#xff0c;提升.Net技能)转自&#xff1a;Ron.liangcnblogs.com/viter/p/10243577.html前言在 .NET Core 2.2中Microsoft.AspNetCore.App 默认内置了EntityFramework Core 包&#xff0c;所以在使用过程中&#xff0c;我们无需再从NuGet仓库单独应用 EFCore 包…

【转】ADO.Net之SqlConnection、 Sqlcommand的应用学习心得(思维导图,知识解析,案例分析)

ADO.Net之SqlConnection、 Sqlcommand的应用 一、思维导图&#xff1a; ADO.NET与SQL连接&#xff1a; 二、知识点介绍&#xff1a; SqlConnection和Sqlcommand都是组成数据提供程序的类。 SqlConnection使用它来建立和数据库的连接&#xff0c;Sqlcommand使用它执行SQL命令…

professional中文_Microsoft office 2007 简体中文版

点击蓝字 关注小白软件名称&#xff1a;Microsoft office 2007软件语言&#xff1a;简体中文软件大小&#xff1a;0.67GB安装环境&#xff1a;Win10/8/7下载链接&#xff1a;https://pan.baidu.com/s/1DybozQ-rn-t2s2EC7FrvFw 提取码&#xff1a;zoa0安装步骤1、解压安装包&…

【转】设计模式 ( 十七) 状态模式State(对象行为型)

设计模式 ( 十七) 状态模式State&#xff08;对象行为型&#xff09; 1.概述 在软件开发过程中&#xff0c;应用程序可能会根据不同的情况作出不同的处理。最直接的解决方案是将这些所有可能发生的情况全都考虑到。然后使用if... ellse语句来做状态判断来进行不同情况的处理。…

【OSG学习】准备开发调试环境

环境 本人当前使用的环境是VS2013 Ultimate Update5 WIN10。 OSG编译安装 下载编译OSG源码不要克隆github上的源码&#xff0c;而是下载稳定版本的OSG源码进行编译。因为github上的源码处于开发阶段&#xff0c;我们应该优先选择稳定版本&#xff0c;我使用的是OpenSceneGr…

【Qt开发经验】Qt信号槽连接不成功问题原因汇总

以下几种情况会导致信号槽连接不成功&#xff0c;下面分别描述。 1. 拥有信号槽的类&#xff0c;必须继承QObject&#xff0c;声明Q_OBJECT宏。 Qt帮助手册里搜索 “Signals & Slots”&#xff0c;可以看到以下说明&#xff1a; 想要使用信号槽&#xff0c;必须继承QOb…

【转】.Net中的异步编程总结

一直以来很想梳理下我在开发过程中使用异步编程的心得和体会&#xff0c;但是由于我是APM异步编程模式的死忠&#xff0c;当TAP模式和TPL模式出现的时候我并未真正的去接纳这两种模式&#xff0c;所以导致我一直没有花太多心思去整理这两部分异步编程模型。今天在CodeProject上…

学习swing鼠标点击事件心得体会_西门子COMOS软件开发定制学习8-查询列表间的数据交互...

​本篇在西门子COMOS软件开发定制学习6-管理界面定制基础上定制&#xff0c;简单介绍两个查询列表之间的数据交互。实现效果&#xff1a;在左侧列表中选择某一设备&#xff0c;右侧列表自动根据所选设备&#xff0c;显示该设备相关的设计图纸(如PID图纸或电气图纸等)&#xff1…