C++内存管理机制(侯捷)笔记4(完结)

2024-01-13 01:28

本文主要是介绍C++内存管理机制(侯捷)笔记4(完结),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

C++内存管理机制(侯捷)

本文是学习笔记,仅供个人学习使用。如有侵权,请联系删除。

参考链接

Youtube: 侯捷-C++内存管理机制

Github课程视频、PPT和源代码: https://github.com/ZachL1/Bilibili-plus

介绍

下面是第四讲和第五讲的笔记。

第四讲:loki库的allocator

第五讲:other issues,主要介绍GNU C++提供的其他分配器的使用

截至2024年1月12日,花费3天时间完成《C++内存管理——从平地到万丈高楼》的学习。

文章目录

  • C++内存管理机制(侯捷)
    • 介绍
    • 45 上中下三个classes分析
    • 46 Class Chunk分析
    • 47 class FixedAllocator分析(上)
    • 48 class FixedAllocator分析(下)
    • 49 Loki::allocator总结
    • 50 GNU C++对allocators的描述
    • 51 VS2013标准分配器&G4.9标准分配器与new_allocator以及G4.9malloc
    • 52 G4.9array_allocator
    • 53 G4.9 debug_allocator
    • 54 bitmap_allocator上
    • 55 bitmap_allocator(下)
    • 后记

45 上中下三个classes分析

loki的allocator

Loki 是 C++ 中一个开源的库,其中包含一组通用的 C++ 组件。在 Loki 库中,有一个称为 Loki::Allocator 的组件,它是一个用于内存分配的工具。

讲这个分配器是为了和GNU C++中的alloc分配器作比较,alloc分配器最后的内存并没有还给操作系统。

Loki allocator的三个类,从低阶到高阶分别为:Chunk, FixedAllocator, SmallObjAllocator。

在这里插入图片描述

Chunk

pData_: unsigned char* // 指针,指向分配的一个chunk
firstAvailableBlock_:unsigned char // 第一个可用区块
blocksAvailable_: unsigned char // 目前还可以供应几个区块

FixedAllocator

chunks_: vector<Chunk> // vector里面放了很多chunk
// 两个指针,指向某两个Chunk
allocChunk_: Chunk* 
deallocChunk_: Chunk*

SmallObjAllocator

pool_: vector<FixedAllocator>  // 里面放了很多FixedAllocator
// 两个指针,指向某两个FixedAllocator
pLastAlloc: FixedAllocator*
pLastDealloc: FixedAllocator*
chunkSize: size_t
maxObjectSize: size_t

46 Class Chunk分析

Loki allocator里面的Chunk

Init函数:new调用malloc创建对象,分配一大块chunk

调用Reset函数,下图中可用的block个数为64,第一个可以的block的编号为0,然后一个for循环,把每个block的第一个字节当作索引index使用(类似于嵌入式指针)

还有Release函数,使用delete[],释放空间,还给操作系统。

在这里插入图片描述

Chunk的Allocate函数

分配一个可用区块(最高优先权),然后firstAvailableBlock_指向下一个可用区块,比如这里可用区块的索引从左边的4变成了右边的3,剩余区块个数也相应调整。

在这里插入图片描述

Chunk的Deallocate函数

释放的指针p已经确定在这个Chunk中,但是p是这个Chunk中的第几个block呢?用p指针减去头指针然后除以每个block的大小,得到该释放的block的索引,这个释放回收的块block具有最高优先权,成为firstAvailableBlock_,然后可利用的block个数+1.

在这里插入图片描述

47 class FixedAllocator分析(上)

FixedAllocator

chunks_: vector<Chunk> // vector里面放了很多chunk
// 两个指针,指向某两个Chunk
allocChunk_: Chunk* 
deallocChunk_: Chunk*

allocChunk_指向最近一次满足分配动作的Chunk,deallocChunk _指向最近一次回收的Chunk。因为有很多chunk,要指定最近用过的chunk,这符合数据的局部性原理。

Allocate的逻辑如下图所示:如果有最近的allocChunk_,那么直接取区块,否则就要for循环从头开始遍历每个chunk,直到找到有可用空间的chunk。return allocChunk->Allocate(blockSize_);是向这个chunk取区块。

在这里插入图片描述

48 class FixedAllocator分析(下)

第二级FixedAllocator类的Deallocate函数,调用VicinityFind函数查找还回来的指针p落在哪个chunk,找到之后交给第一级Chunk类的Deallocate来处理。

VicinityFind(临近搜寻)函数如下:功能是进行查找。

chunkLength是chunk的大小,后面查找的时候要用

lo是上次还回来的chunk,hi是下一个chunk

loBound和hiBound是vector的头跟尾

整体思路是:并分两路,一路往上查找,一路往下查找。

在lo里面找,找不到的话就往上面的chunk去查找,一直往上,一个接一个的chunk去找。

然后在hi里面找,找不到就往下面的chunk去查找,一直往下,一个接一个的chunk去找。

在这里插入图片描述

下面是FixedAllocator类的DoDeallocate函数

调用Deallocate函数进行回收,然后确认是否是全回收的情况处理。

全回收的时候,需要确认有2个chunk,才回收一个,就是上文讲到的deferring延缓回收。

在这里插入图片描述

49 Loki::allocator总结

很简单的方式判断chunk全回收,指的是记录可用区块个数,当它变成0的时候表示全分配出去,当它恢复到原来的状态,就表示可全回收。

在这里插入图片描述

50 GNU C++对allocators的描述

第五讲 other issues

GNU C++ 对于allocator的描述

在这里插入图片描述

下面介绍两个分配器: new_allocator 和malloc_allocator,它们都没有特别的动作,无非底部调用operator new和malloc。它们没有用内存池。

在这里插入图片描述

另一种做法是使用智能型的allocator,使用内存池,分一大块然后切分成小块。

这类allocator有bitmap_allocator, pool_allocator, _mt_alloc(multithread多线程的分配器)

在这里插入图片描述

GNU C++ 提供三种测试,用于测速:插入数据测试,多线程状态下的插入和删除测试,多线程的生产者和消费者模型测试。

在这里插入图片描述

另外两个智能型allocator是 debug_allocator 和 array_allocator

在这里插入图片描述

51 VS2013标准分配器&G4.9标准分配器与new_allocator以及G4.9malloc

VS2013版本的标准分配器allocator,里面的allocate函数就是调用operator new,底层调用malloc,这是一种没有包装的分配器。

在这里插入图片描述

GNU C++4.9版本的标准分配器 allocator,继承自new_allocator,里面的动作也是operator new 和operator delete,也是没有包装的分配器。

在这里插入图片描述

GNU C++4.9版本的malloc_allocator,里面就是调用malloc和free,没有包装。

在这里插入图片描述

52 G4.9array_allocator

array_allocator

指针_M_array指向一个C++数组,静态数组不需要释放,归还

在这里插入图片描述

看看array_allocator怎么使用

int my[65536];
array_allocator<int, array<int, 65536>> myalloc(&my); // 传入数组的地址

在这里插入图片描述

第二种用法,是用动态的new一块数组,其他用法与上面一致。

在这里插入图片描述

53 G4.9 debug_allocator

debug_allocator是一个包装器,把另一个分配器包装进来,这里是_Alloc分配器,定义为_M_allocator,实际分配的时候就是调用这个分配器的allocate,分配n + extra个大小的空间。

在这里插入图片描述

GNU C++2.9容器使用的不是std::allocator,而是std::alloc,这是一个好的分配器。

在这里插入图片描述

在GNU C++4.9版本的__pool_alloc就是上面2.9版本的alloc。它的缺点是什么呢?只拿不还,不把分配的空间还给操作系统。

在这里插入图片描述

GNU C++4.9版本 __pool_alloc测试用例

在这里插入图片描述

54 bitmap_allocator上

这里重要的是_M_allocate_single_object_M_deallocate_single_object,这是什么意思呢?所有的allocator都是供给容器使用的,容器每次请求的都是一个元素的内存分配,所以这里就是单独处理一个object的请求。当不是一个object的时候,就会退化到operator new和operator delete,但是这种情况一般遇不到。

在这里插入图片描述

当客户是容器的使用,容器要的一个元素的内存空间就是block,如果是std::list的时候,这个block还包括node里面的指针。

下图中64blocks指的是一次性挖64个blocks开始供应,这个是两倍成长,下次挖128个,再是256个,…

挖的这些blocks,加上前面的bitmap,还有前面的数值,一起称为super block。

bitmap中是64bits(对应于blocks的数量),每个bit表示单个block的状态,bitmap是unsigned int,一个bitmap是32位,只能记录32个blocks的情况。这里是64个blocks,需要2个bitmap来记录。

前面还有一个整数use count,记录有几个block被分配

最前面还有一个整数,记录super block的大小

有一个mini vector(模拟标准库中的vector写出来的)来操纵super block,里面的start指针和finish指针分别指向super block的头和尾

在这里插入图片描述

现在客户需要分配内存:下面深灰色这个block被分配出去,use count = 1,然后bitmap[0]最后一位变成0,表示已经分配出去

在这里插入图片描述

然后请求分配第二个block,下面两个灰色的block表示被分配出去,use count = 2,bitmap[0]后两位变成0,表示这两个block被分配出去。

在这里插入图片描述

后面一直分配block出去,下图表示已经分配出去63个block,对应的use count = 63,bitmap[1]和bitmap[0]变成80000000H和00000000H,只有最后一个block对应的bitmap中的bit才为1,表示未被分配出去。

在这里插入图片描述

下面客户归还其中一个block,对应的bitmap要变成1,表示未分配(回收回来),然后use count由63变成62

在这里插入图片描述

当1个super block(有64个blocks)用完后,开始启用第二号super block,这时候super block中的block块数由64扩大两倍变成128个,由4个bitmap整数表示。

而且mini vector中由一个单元,变成两个单元,每个单元指向一个super block。

在这里插入图片描述

第二个super block用完(前面已经用完第一个super block,其含有64个blocks,第二个super block包含128个blocks,也已经用完),启用第三个super bloc,其包含的blocks为128 x 2 = 256个。

mini vector中也有第三个单元来控制这个super block。

在这里插入图片描述

55 bitmap_allocator(下)

上面谈的是bitmap_allocator的分配,下面谈它的回收(容器归还元素空间)机制

第一个super block全回收,用另一个mini vector(称为free list)中的entry指针指向这个super block,表示已经回收。

如果下次新分配一个super block,它其中blocks的数量要减半,如下图所示,如果前三个superblocks大小分别为64个,128个,256个,由于第一个superblock被回收,那么下次分配的superblock大小由已经分配的最大值256变成一半,为128个blocks。

原来的mini vector里面的指向回收的这个superblock头和尾的entry被删除。

在这里插入图片描述

第二个super block也全回收,则free list里的entry加一个,指向这个super block。

原来的mini vector里面指向这个superblock头尾的entry被删除。

在这里插入图片描述

第三个super block被全回收,被free list的指针指向。

在这里插入图片描述

后记

截至2024年1月12日22点09分,完成《C++内存管理机制》的第四讲和第五讲的笔记。主要涉及Loki库中allocator的设计,以及对GNU C++4.9其他allocator的介绍,主要包含bitmap_allocator。

这篇关于C++内存管理机制(侯捷)笔记4(完结)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/599799

相关文章

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java内存分配与JVM参数详解(推荐)

《Java内存分配与JVM参数详解(推荐)》本文详解JVM内存结构与参数调整,涵盖堆分代、元空间、GC选择及优化策略,帮助开发者提升性能、避免内存泄漏,本文给大家介绍Java内存分配与JVM参数详解,... 目录引言JVM内存结构JVM参数概述堆内存分配年轻代与老年代调整堆内存大小调整年轻代与老年代比例元空

从入门到精通C++11 <chrono> 库特性

《从入门到精通C++11<chrono>库特性》chrono库是C++11中一个非常强大和实用的库,它为时间处理提供了丰富的功能和类型安全的接口,通过本文的介绍,我们了解了chrono库的基本概念... 目录一、引言1.1 为什么需要<chrono>库1.2<chrono>库的基本概念二、时间段(Durat

C++20管道运算符的实现示例

《C++20管道运算符的实现示例》本文简要介绍C++20管道运算符的使用与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录标准库的管道运算符使用自己实现类似的管道运算符我们不打算介绍太多,因为它实际属于c++20最为重要的

Visual Studio 2022 编译C++20代码的图文步骤

《VisualStudio2022编译C++20代码的图文步骤》在VisualStudio中启用C++20import功能,需设置语言标准为ISOC++20,开启扫描源查找模块依赖及实验性标... 默认创建Visual Studio桌面控制台项目代码包含C++20的import方法。右键项目的属性:

c++中的set容器介绍及操作大全

《c++中的set容器介绍及操作大全》:本文主要介绍c++中的set容器介绍及操作大全,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录​​一、核心特性​​️ ​​二、基本操作​​​​1. 初始化与赋值​​​​2. 增删查操作​​​​3. 遍历方

解析C++11 static_assert及与Boost库的关联从入门到精通

《解析C++11static_assert及与Boost库的关联从入门到精通》static_assert是C++中强大的编译时验证工具,它能够在编译阶段拦截不符合预期的类型或值,增强代码的健壮性,通... 目录一、背景知识:传统断言方法的局限性1.1 assert宏1.2 #error指令1.3 第三方解决

C++11委托构造函数和继承构造函数的实现

《C++11委托构造函数和继承构造函数的实现》C++引入了委托构造函数和继承构造函数这两个重要的特性,本文主要介绍了C++11委托构造函数和继承构造函数的实现,具有一定的参考价值,感兴趣的可以了解一下... 目录引言一、委托构造函数1.1 委托构造函数的定义与作用1.2 委托构造函数的语法1.3 委托构造函

C++11作用域枚举(Scoped Enums)的实现示例

《C++11作用域枚举(ScopedEnums)的实现示例》枚举类型是一种非常实用的工具,C++11标准引入了作用域枚举,也称为强类型枚举,本文主要介绍了C++11作用域枚举(ScopedEnums... 目录一、引言二、传统枚举类型的局限性2.1 命名空间污染2.2 整型提升问题2.3 类型转换问题三、C

C++链表的虚拟头节点实现细节及注意事项

《C++链表的虚拟头节点实现细节及注意事项》虚拟头节点是链表操作中极为实用的设计技巧,它通过在链表真实头部前添加一个特殊节点,有效简化边界条件处理,:本文主要介绍C++链表的虚拟头节点实现细节及注... 目录C++链表虚拟头节点(Dummy Head)一、虚拟头节点的本质与核心作用1. 定义2. 核心价值二