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,一经查实,立即删除!

相关文章

【转】C#中的命名空间namespace全解

.NET Framework 类库提供下列命名空间&#xff1a; 【Microsoft】 Microsoft.CSharp 包含支持用 C# 语言进行编译和代码生成的类。 Microsoft.JScript 包含支持用 JScript 语言进行编译和代码生成的类。 Microsoft.VisualBasic 包含支持用 Visual Basic .NET 语言进行编译和…

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

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

Qt-VS开发:解决VS中使用带有信号槽的导出对象库时,信号槽不工作的问题

解决办法 需要把导出库的头文件加入到调用此动态库的项目中&#xff0c;只加入到搜索目录中是不行的。 加入到项目中后&#xff0c;VS会自动生成moc_xxx.cpp文件&#xff0c;可以在GeneratedFiles/Debug或GeneratedFile/Release中找到&#xff0c;必须有文件才能实现信号槽。

C# 编程规范

一、命名 1.用pascal规则来命名方法和类型. publicclass TextBox { publicvoid DataBind() { } } 2.用camel规则来命名局部变量和方法的参数&#xff0c; string strUserName;public AddUser(string userId, byte[] passWord); (并加前缀 数据类型&#xff0c;可选。)s…

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 以下系统…

解决Qt graphis-view框架中,上层图元接收hover事件导致底层图元接收不到的问题

问题重现 两个图元&#xff0c;一个在上面&#xff0c;名为item_up&#xff0c;一个在下面&#xff0c;名为item_below。 当item_up->setAcceptHoverEvents(true)时&#xff0c;item_up可以接收到鼠标悬停事件&#xff08;hoverEnter&#xff0c;hoverMove&#xff0c;hove…

【转】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;但是就是莫名其妙的报…

Qt事件传递相关问题

事件传递 涉及到深层次窗口结构或者窗口封装时&#xff0c;经常会遇到上层窗口接收不到事件的问题。排除这类问题需要了解窗口间的事件传递过程。 应用程序产生事件时&#xff0c;事件会先派发给父窗口&#xff0c;由父窗口在内部派发给子窗口&#xff0c;子窗口会进一步传递…

tableau 实战练习数据源分享_小白入行数据分析师3年-工作内容复盘分享含代码(二)-数据库及Tableau篇介绍...

前言本文是对使用的数据库以及Tableau的内容进行介绍&#xff0c;方便学习者了解数据库方向有哪些内容是需要有所了解以及可以注意的&#xff0c;分享内容基于个人对这些技能的理解&#xff0c;如有错误请及时指出&#xff0c;我会立马改进。数据库篇数据库这边&#xff0c;我的…

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

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

【转】WebSocket协议:5分钟从入门到精通

一、内容概览 由于WebSocket的出现&#xff0c;使得浏览器也具备了实时双向通信的能力。本文由浅入深&#xff0c;介绍了WebSocket建立连接、交换数据的细节&#xff0c;以及数据帧的格式。此外&#xff0c;还简要介绍了针对WebSocket的安全攻击&#xff0c;以及协议是如何抵御…

Qt填坑

Q_ASSERT里面不要放需要执行的语句&#xff0c;否则release下此语句不会被执行。父类中含Q_OBJECT&#xff0c;子类并不能使用信号槽机制&#xff0c;子类也需要加Q_OBJECT。QStringLiteral只能在Qt 5及以上版本中使用&#xff0c;低版本不支持。如果需要Qt 5及以上版本的代码放…

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

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

Git学习资料

Git使用简易指南官方手册Git北京Git工作流

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

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

python代码实例sicket_Python socket聊天脚本代码实例

这篇文章主要介绍了Python socket聊天脚本代码实例,文中通过示例代码介绍的非常详细&#xff0c;对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 没有前端&#xff0c;多开了一条线程用于接收信息。 服务器端&#xff1a; # -*- coding:utf-8 -*- import so…

【转】01Teams的前世今生

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