C++STL内存配置的设计思想与关键源码分析

【STL】空间配置器(二):二级空间配置器 SGI STL一级空间配置器: http://blog.youkuaiyun.com/bit_clearoff/article/details/53503846前面我们已经分析过了SGI STL一级空间配置器的实现以及其中的相关机制,今天我们主要来分析一下空间配置器中的重头戏:二级空间配置器。我们先来用图描述一下二级空间配置器的结构: 上图是二级空间配置器的基本结构是有一块事先分配好内存的内存池和一个给对象 阅读详情

本文转载自 http://www.cnblogs.com/mengwang024/p/4461116.html

0. 说明

我认为要读懂STL中allocator的源码,并汲取它的思想,至少以下几点知识你要了解:operator new和operator delete、handler函数以及一点模板知识。否则,下面你很可能看不大明白,补充点知识再学习STL源码比较好。

下面会结合关键源码分析C++STL(SGI版本)的内存配置器设计思想。关键词既然是“思想”,所以重点也就呼之欲出了。


1. allocator的简短介绍

我阅读的源码是SGI公司的版本,也是看起来最清楚的版本,各种命名最容易让人看懂。allocator有人叫它空间配置器,因为空间不一定是内存,也可以是磁盘或其他辅助存储介质。我说的内存配置就是指的allocator。

C++标准规范了allocator的一些必要接口,由各个厂家实现。SGI的版本与众不同,也与标准规范不同,它的名称是alloc而不是allocator,且不接受任何参数。

假设你在程序中显示写出allocator,不能像下面这样写:

vector<int, std::allocator<int> > iv;        //错误的

必须要这样写才对:

vector<int, std::alloc> iv                    //好的

虽然SGI STL并不符合规范,但我们用起来好像很自然。这是因为我们使用时空间配置器是缺省的,不需要我们自行指定。例如,STL中vector的声明如下:

这里写图片描述

注意:下文我基本就用截图来解释代码了,因为我发现比起粘贴代码,这样更清晰(有颜色对比)。


2. 源码文件简单介绍

STL标准规定:STL的allocator定义于<Memory>文件中,<Memory>主要包含了一些头文件,我们主要说的是两个:

这里写图片描述

<stl_alloc.h>负责内存空间的配置与释放;<stl_construct.h>负责对象内容的构造与析构


3. 构造和析构工具:construct()和destroy()

先来说一下简单的<stl_construct.h>文件。这部分也不涉及什么思想,只是有一个版本的destroy()应该认真看看。

(1)构造工具:construct()

construct()只有一个版本:

这里写图片描述

这里使用了placement new表达式(定位new 表达式),它的作用是p指向的内存类型为T1,用value值初始化这块内存。

(2)析构工具

destroy()倒是有几个版本:

第一个版本:

这里写图片描述

这种显示调用析构函数的做法,你也应该要熟悉。

第二个版本:

这里写图片描述

第二个版本可有点说法。调用层次是这样的:destroy-> __destroy-> __destroy_aux,__destroy_aux最终调用第一个版本的destroy。这个版本的destroy接受一对迭代器作为参数,析构迭代器所指向的范围内元素。

讲解这个流程前,先简单说一下trivial_destructor。

如果用户不定义析构函数,而是用编译器合成的,则说明析构函数基本没有什么用(但默认会被调用),称之为trivial destructor。

那么,如果一对迭代器所指向的元素都是trivial destructor的,就没必要浪费时间对每个对象依次执行它的析构函数了,依靠编译器的行为就好了。这样在效率上是一种提升。 这是STL allocator优化的一个点。

首先利用value_type()取得迭代器指向对象的型别,再利用__type_traits<T>判断对象的析构函数是否为trivial_destructor。如果是__true_type就什么都不做,否则循环调用第一版本的destroy()。

第三个版本:

这里写图片描述

这是针对迭代器为char*和wchar_t*的特化版本,看到它们的函数体为空,你应该猜到了,无须执行析构操作。

4. 内存的申请与销毁,std::alloc

内存的申请和销毁由<stl_alloc.h>负责。SGI关于这一点的设计哲学是:

(1)向system heap要求空间。

(2)考虑多线程状态。

(3)考虑内存不足时的应变策略。

(4)考虑过多“小型区块”可能造成的内存碎片问题。

其实我最主要想说的是(3)(4)的设计策略,尤其是内存池的思路。

std::alloc的整体设计思想为:

SGI设计了双层级配置器,第一级配置器直接使用malloc和free;第二级配置器视情况不同采用不同策略:当配置区块超过128bytes时,视为“足够大”,调用第一级配置器处理;当配置区块小于128bytes时,视为“过小”,为降低额外负担,采用memory pool(内存池)处理方式,不再借助于第一级配置器。

一、第一级内存配置器解析

第一级配置器主要函数有:allocate分配内存、deallocate释放内存、reallocate重新分配内存等。

(1)allocate

这里写图片描述

直接调用C函数malloc,如果内存无法满足需求,就调用oom_malloc函数。

这里写图片描述

原来,这是自己实现的handler函数啊,为什么自己实现呢?因为它使用的并不是operator new配置的内存,所以无法使用C++new-handler机制。

关于这个机制,实际上能有不少东西可说呢,如果你不熟悉它的用途或自己实现的方法,我建议你看看《Effective C++》,或者看看我对《Effective C++》做的笔记。我这里主要不是想分析语法方面的东西。

(2)deallocate

这里写图片描述

代码放上去就应该明白了。

(3)reallocate

这里写图片描述

这里依然是调用C的realloc函数,如果调用失败,就调用oom_realloc函数。

这里写图片描述

可以看出oom_realloc也是个handler函数。

基本上第一级内存配置器就解释清楚了。这里再提一点:SGI以malloc而非operator new来配置内存一方面是历史原因,另一方面C++并未提供realloc函数。这样造成了SGI不能直接使用C++的set_new_handler(),只能自己仿真一个。如何仿真set_new_handler,是有特定模式的。

二、第二级内存配置器解析

第二级内存配置器增加了一些机制,避免太多小额区块造成的内存碎片。小额区块带来的不仅是内存碎片,配置时的额外负担也是个大问题。额外负担永远无法避免,毕竟系统要靠这多出来的空间来管理内存,但区块越小,额外负担所占的比例越大,自然越浪费。

第二级内存配置器的整体思想是:

(1)如果申请的区块超过128bytes,就交给第一级内存配置器处理。

(2)如果申请的区块小于等于128bytes,用内存池管理。

具体为:第二级内存配置器会将任何小额区块的内存需求上调至8的倍数,并维护16个free-lists,各自管理大小为8、16、24、32、40、48、56、64、72、80、88、96、104、112、120、128字节的小额区块。

free-lists中节点结构如下:(我已经将这个union注释)

这里写图片描述

注意:union的这种用法,也被称为”柔性数组“成员。本质上,与小端对齐这种存储方式有关,这是一种技巧。

(1)allocate

第二级内存配置器__default_alloc_template的内存分配接口是allocate函数。

这里写图片描述

关键部分我已经用红框注释过了。FREELIST_INDEX(n)函数根据n的值返回16个free-list中合适的那个list的下标。

这里写图片描述

再看看ROUND_UP(n),这个函数我认为写的挺巧妙的,将bytes值调整至8字节的倍数。

这里写图片描述

理解这个函数你可以先举几个bytes值,看看返回值是什么,自然就理解了。refill()函数很有用处,我放在下面再来介绍。

(2)deallocate()

这里写图片描述

首先判断区块大小,大于128字节就调用第一级配置器,否则就根据需要回收的字节大小,判断出应该把它回归到哪个free list,然后由这个free list回收。

(3)refill()

这个函数挺重要的,所以要单独拿出来介绍。当free list中没有可用的区块时,就调用这个函数,为该free list重新填充一部分空间。新的空间取自内存池(由chunk_alloc完成)。缺省取得20个新区块,如果内存池空间不足,获得的新区块数目可能小于20。

这里写图片描述

图中两个红框是值得注意的两个点。一旦从内存池获得内存区块后,拿出一个给调用者,另外的还要找到合适的free list”穿“起来。

(4)内存池函数chunk_alloc()

内存池一直用来供给free list。下面要将这个函数分开截图说明了。

这里写图片描述

第一个分支:看看内存池内的容量够不够,够的话直接拿走就好。

这里写图片描述

第二个分支:内存池不够20个块的容量,但是大于等于1个块的长度,就把剩下的都给出去了。此时,内存池是空的。

这里写图片描述

第三个分支:如果内存池连1个对应的块都不能提供了,比如需要32字节,但只有8字节了,这时候最好的做法是把这8个字节链接到相应的free list利用上。不出意料,此时内存池也是空的。

这里写图片描述

第四个分支:内存池空空如也,所以内存池求助于运行时堆,堆也没有那么多空间了,于是就检查这16个free list中有哪些块没用过呢,把这些补充到内存池。

这里写图片描述

第五个分支:没错,heap也无能为力了,内存池干脆直接调用第一级配置器,因为第一级配置器有new-handler机制,或许有机会释放其他内存拿来此处调用呢。如果可以,就成功否则抛出bad-alloc异常。


4. 小结

如果别人问我STL内存配置的思想。我可能会这样说:C++STL是两级配置内存的,具体来说:第一级负责管理大块内存,要保证有类似new-handler的机制;第二级负责管理小块内存,为了更好的管理内存碎片,建立16个链表,每个链表“穿”着一块一块固定大小的内存,这16个链表(0至15)分别“穿”的内存是8、16、24…128倍数关系。需要内存时,从“合适”的链表取走,如果“合适”的链表内存不够用了,从内存池里拿,如果内存池不够用了,从运行时heap里拿,如果heap也溢出了,就交给第一级配置器,因为它有new-handler机制。

所以,堆上的东西用完了赶紧换回来,别让内存池着急。

《STL源码剖析》深入剖析理解 《STL源码剖析》是由侯捷所著,是一本经典的C++ STL源码解析书籍,它深入剖析SGI版本的C++ STL源代码,解释了STL各个组件的实现原理和设计思路。 阅读详情

相关推荐

深入解析SGI版C++ STL源码

htmltable {th, td {th {pre {简介:C++标准模板库(STL)是C++编程的核心组件,提供了高效的数据结构和算法。本课程深入分析SGI(Stanford Graphics Interface)版本的STL源码,探讨容器、迭代器、算法和函数对象等组成,并了解SGI版本的特点,如迭代器强度、Rope高效字符串处理和堆的优先队列实现。源码分析包括容器的内存管理、迭代器设计原理、算法优化以及模板元编程的应用。

weixin_36123300的博客 563

【C++拾遗】 从内存布局看C++虚继承的实现原理

准备工作 1、VS2012使用命令行选项查看对象的内存布局 微软的Visual Studio提供给用户显示C++对象在内存中的布局的选项:/d1reportSingleClassLayout。使用方法很简单,直接在[工具(T)]选项下找到“Visual Studio命令提示(C)”后点击即可。切换到cpp文件所在目录下输入如下的命令即可       c1 [filename].cpp

上善若水,人淡如菊 2万+

设计模式+STL代码示例

设计模式+STL代码示例 设计模式+STL代码示例 设计模式+STL代码示例

Linux中C++程序读取占用内存的两种方式:meminfo / gperftools

1. 利用/proc/meminfo文件 直接读取系统当前可用内存,在程序执行前和执行中进行调用,系统可用内存的减少近似表示程序占用的内存。 直接调用下面这个函数: int GetSysMemInfo() { //获取系统当前可用内存 int mem_free = -1;//空闲的内存,=总内存-使用了的内存 int mem_total = -1; //当前系统可用总内存 int mem_buffers = -1;//缓存区的内存大小

suzhou7915的博客 4459

保姆级教程:在华为云Ubuntu服务器上快速搭建RAGflow开发环境(避坑指南)

本文提供了一份在华为云Ubuntu服务器上快速搭建RAGflow开发环境的保姆级教程。针对网络拉取镜像等常见问题,详细介绍了从服务器选型、系统优化、Docker配置到RAGflow部署与验证的全流程,并给出生产环境调优建议,帮助开发者高效避坑,快速构建企业级应用环境。

weixin_29250403的博客 85

软考软件评测师备考避坑指南:过来人告诉你这5个章节最容易丢分(附2024最新考纲解析)

本文为软考软件评测师考生提供备考避坑指南,深度解析5个高频失分章节,包括标准化与信息化基础、中间件技术、软件测试模型、白盒测试覆盖准则和性能测试指标。文章结合2024最新考纲,分享实战技巧和记忆口诀,帮助考生精准打击失分重灾区,提升备考效率。

weixin_29223603的博客 467

C++ 类 内存分布 虚函数 单继承 多继承

C++ 类 内存分布 虚函数 单继承 多继承 重点内容 一、首先看看没有继承情况下类的内存分布: 1.1 定义一个简单的类,没有虚函数。 代码如下:#include<iostream> using namespace std; class Base { public: int Base_1; int Base_2; public: void func1(); }; i

徐麻子的专栏 2303

Matlab Arduino 硬件支持包安装 MATLAB_Support_Package_for_Arduino_Hardware

Matlab Arduino硬件支持包离线安装

weixin_43283734的博客 2840

浅谈C++中内存分配、函数调用和返回值问题

在谈述函数调用和返回值问题之前,先来看看C++中内存分配的问题。 C++编译器将计算机内存分为代码区和数据区,很显然,代码区就是存放程序代码,而数据区则是存放程序编译和执行过程出现的变量和常量。数据区又分为静态数据区、动态数据区,动态数据区包括堆区和栈区。 以下是各个区的作用: (1)代码区:存放程序代码; (2)数据区 a.静态数据区: 在编译器进行编译的时...

weixin_30675967的博客 286

LLC谐振电路增益公式推导

LLC谐振电路增益公式理解

金凯*博客 1万+

无人车路径规划方法汇总

无人车路径规划方法汇总

weixin_44380244的博客 2558

c++中STL库 简介 及 使用说明

作为C++标准不可缺少的一部分,STL应该是渗透在C++程序的角角落落里的。STL不是实验室里的宠儿,也不是程序员桌上的摆设,她的激动人心并非昙花一现。本教程旨在传播和普及STL的基础知识,若能借此机会为STL的推广做些力所能及的事情,到也是件让人愉快的事情。    初识STL:解答一些疑问    1.1 一个最关心的问题:什么是STL    "什么是STL?",假如你对STL还

tanqiuwei的专栏 4万+

开放自己本机的mysql允许别人连接

需要在左下角搜索记事本应用,右键以管理员身份运行,之后按照上述地址找到my.ini文件,ctrl+F搜索到[mysqld],修改或添加 bind-address 设置,将其更改为 0.0.0.0 或你的计算机的 IP 地址。打开 MySQL 的配置文件 my.ini。username和密码是第3步创建能从远程地址访问的用户和密码,ip地址是ipconfig查看的地址(前提是能ping通,才能连接该地址对应的数据库)。使用 MySQL 命令行连接到自己的数据库,并创建一个能够从远程地址访问的用户。

m0_74824687的博客 1167

C++ STL全面解析:六大核心组件之一----分配器(STL进阶学习)

C++标准模板库(STL)是一组高效的数据结构和算法的集合,广泛应用于C++程序设计中。

LKHzzzzz的博客 1217

TC3XX - MCAL知识点(一):新建Tc3xx EB-tresos工程

基于EB-tresos建立Hightec或者Tasking工程的时候,第一个步骤就是需要新建一个EB-tresos的工程,本文记录一下如何新建一个EB-tresos工程,以及对其进行简单的操作。那么Tc3xx的MCAL包含哪些内容呢?

梦想技术家 2968

【ComfyUI】Flux 图像扩展

本文介绍了一个基于Flux扩图技术的ComfyUI工作流,能够智能扩展图像尺寸并保持内容连贯性。该流程通过扩散模型与条件编码的结合,对图像边缘进行语义填充生成,适用于艺术创作、海报设计等场景。工作流包含图像加载、外扩填充、文本编码、扩散采样等核心节点,采用Flux1-fill-dev等专业模型确保生成质量。演示效果显示,系统能自然延伸原图内容,保持风格统一。该技术可广泛应用于插画扩展、摄影增强、游戏场景构建等领域,显著提升创意工作效率。

Mr数据杨 928

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

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

鱼思故渊的专栏 1005

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

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

本文讲解SGI STL空间配置器的第二级配置器。 相比第一级配置器,第二级配置器多了一些机制,避免小额区块造成内存的碎片。不仅仅是碎片的问题,配置时的额外负担也是一个大问题。因为区块越小,额外负担所占的比例就越大。 额外负担是指动态分配内存块的时候,位于其头部的额外信息,包括记录内存块大小的信息以及内存保护区(判断是否越界)。要想了解详细信息,请参考MSVC或者其他malloc实现。

鱼思故渊的专栏 1749

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

SGI STL二级空间配置器源码剖析(1)

之前大概写过SGI STL相关的东西,讲了讲空间配置器的原理,这一系列就剖析一下源码。在C++STL里空间配置器做的事情就是分离了对象内存的开辟,释放,对象构造,析构我们看SGI STL的实现还是找到vector看他push_back实现会发现他的构造析构都是定义的全局的函数模板SGI STL两个allocator的实现还是一级的是malloc和free,和C++STL基本无差,不过二级的用内存池的方式实现就复杂了进到空间配置器源码看看。

oorik的博客 597

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

stl源码剖析总体

优化程序性能:根据容器的特性选择合适的容器,避免不必要的性能损耗。扩展STL功能:理解仿函数和配接器的原理,实现自定义的策略和功能。提升编程能力:学习泛型编程和模板元编程技术,提高代码的复用性和可维护性。建议阅读STL的经典书籍,如《STL源码剖析》,结合实际的代码实践,加深对STL的理解和应用。

sina123的博客 534

深入解剖STL Vector:从底层原理到核心接口的灵活运用

本文结合作者钻研《STL源码剖析》与亲身实践,系统解析了C++ vector容器的实现与运用。文章从数据结构与迭代器设计入手,深入剖析内存管理、构造析构及元素操作等底层机制,并通过代码示例详解resize、insert等核心接口的性能特性与实践技巧。无论是刚接触STL的开发者,还是希望深入理解设计原理的进阶者,都能从中获得从使用到透彻理解的清晰路径,在掌握实用技能的同时感受源码学习的独特价值。

燃于AC之乐的博客 2422

C++标准库STL部分代码学习(源码之前,了无秘密)

include// alloc是SGI STL的空间配置器public:// vector的嵌套类型定义,typedef用于提供iterator_traits支持protected:// 这个提供STL标准的allocator接口// 表示目前使用空间的头// 表示目前使用空间的尾// 表示实际分配内存空间的尾// 释放分配的内存空间// 由于使用的是data_allocator进行内存空间的分配,

2501_90252715的博客 917
上一篇: Unity5.x实现简易语音聊天(三)Client-Server通信
下一篇: MySQL索引背后的数据结构及算法原理
tab_space
博客等级 码龄11年 91粉丝 29原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值