当前位置: 首页>前端>正文

C++内存管理学习

经典图片

C++内存管理学习,第1张
经典图片

1. 申请内存的方式

C++内存管理学习,第2张
四种申请内存的方式
C++内存管理学习,第3张
四种申请内存的方式示例
C++内存管理学习,第4张
new

new做以上三个事情,1申请一片内存,然后调用构造函数。


C++内存管理学习,第5张
new handle的使用场景
C++内存管理学习,第6张
placement new
C++内存管理学习,第7张
std::string 对于operator new的使用

std::string 对应与stl中的实现类名为basic_string,其中为了实现reference count的功能,所以重载了operator new成员函数,使得每次申请的内存会比实际大小多一个extra的长度

C++内存管理学习,第8张
delete

delete做两个事情,1调用析构函数,2调用free释放内存

C++内存管理学习,第9张
array new & delete

左边其实不会内存泄露,因为free的时候需要管理的长度其实记录在cookie里面,所以即使没有使用delete [] 也不会导致内存泄漏,当使用delete时,会正确释放对应长度的内存块,但是只会调用一次析构函数,因为Complex这个类的析构函数没有实际意义,所以不会导致内存泄漏;
而右边string array会导致内存泄漏如果使用delete,而不是delete[],因为string类的析构函数会释放类内的一个指针,所以如果没有调用数组中所有对象的析构函数,那么就会发生内存泄漏

C++内存管理学习,第10张
new、 operator new、::operateor new, malloc 之间的关系

new 解析重载

C++内存管理学习,第11张
operator重载示例
C++内存管理学习,第12张
内存布局 & cookie

61h是错误的,正确的是60h,这是一个10进制数
计算方式如上所示:4+32+4+12*3+4+12+4=96=60h
pad是为了做16的倍数对齐

注:vc6.0 malloc的内存布局实际图如下:

C++内存管理学习,第13张
VC6.0 malloc

通过重载成员函数operator new和operator delete,可以去掉cookie,节省内存使用,另外也可以减少malloc的调用,这种方式其实就相当于实现一个简单的内存池:

C++内存管理学习,第14张
内存池版本1的实现:

C++内存管理学习,第15张
间隔减少证明确实cookie没有了
C++内存管理学习,第16张
内存池版本2的实现:

C++内存管理学习,第17张
内存池版本3的实现

C++内存管理学习,第18张
内存池版本3的实现

C++内存管理学习,第19张
内存池版本4的实现

以上版本1----->到版本4的过程就是operator new到stl种allactor中发展演进的大致过程,一种标准库的allocator实现,这个可以匹配所有的类大小,有10种大小类型的链表,供选择


C++内存管理学习,第20张
标准库的allator实现

2. 标准库std::allocator的实现

C++内存管理学习,第21张
VC6.0 标准库allocator的实现
C++内存管理学习,第22张
BC5 标准库allocator的实现
C++内存管理学习,第23张
GNU2.9 标准allocator的实现

C++内存管理学习,第24张
GNU2.9 分配器的实现有多个版本
C++内存管理学习,第25张
实际GNU2.9 是标准库使用的alloc

C++内存管理学习,第26张
GNU4.9 标准allocator的实现

C++内存管理学习,第27张
GNU4.9 不同版本分配器的测试

优秀的设计——G2.9 std::alloc运行模式

C++内存管理学习,第28张
G2.9 std::alloc运行模式
  • 每个节点都是8个字节的倍数,所以申请内存的时候最好进行8字节对齐
  • 大概运行流程如下:(更详细的过程请看下文
    当有容器使用时,申请内存会调用一次alloc::allocator,那么会根据单个元素的大小,申请对应的20*2个元素大小的连续内存空间;(20是团队经验值,没有具体的源头,2是每次申请两倍的内存空间,一半划分为20个元素大小空间,使用链表链接起来,另外一半待下次有不同类型元素需要内存是供其使用)
    如上图,假设第一个vector被使用时,T的类型是32字节,那么在free_list[16]的坐标为3的位置,会使用malloc申请出20*2*32字节的内存空间,其中起始20个被划分为32字节一块提供给vector使用,剩下一半作为备用;然后第二个容器list被使用,T的类型是64字节那么,那么会先使用free_list中剩余的没被使用的空闲内存,在坐标为3的位置,会将上次分配的20*32字节,划分为10*64的区间供list使用;紧接着第三个容器deque被使用时,T的类型是96字节,那么由于当前free_list没有空闲内存,那么会在坐标为11的位置申请20*2*96大小的内存空间,其中一半被划分为20*96的区块,供deque使用,剩下一半作为备用池子供下次使用
C++内存管理学习,第29张
对应每个节点的空闲链表的方式

空闲链表的头会以union的结构作为头指针,如果被占用,移向下一个位置

std::alloc运行顺序

C++内存管理学习,第30张
第一步

C++内存管理学习,第31张
第二步

C++内存管理学习,第32张
第三步

C++内存管理学习,第33张
第四步

注意这里追加量会越来越大,计算方式是(累计量/16)

C++内存管理学习,第34张
第五步

C++内存管理学习,第35张
第六步

C++内存管理学习,第36张
第七步

C++内存管理学习,第37张
第八步

注意这里产生一个内存碎片, 会被安全链接到对应的节点位置,作为一个内存块,这样就安全处理了这一内存碎片

C++内存管理学习,第38张
第九步

C++内存管理学习,第39张
第十步

以上皆为正常流程,从下一页开始出现异常,无法申请更多的内存

C++内存管理学习,第40张
第十一步

C++内存管理学习,第41张
第十二步

C++内存管理学习,第42张
第十三步-上

C++内存管理学习,第43张
第十三步-下

std::alloc源码分析

C++内存管理学习,第44张
1

C++内存管理学习,第45张
2

C++内存管理学习,第46张
3

C++内存管理学习,第47张
4

C++内存管理学习,第48张
5-上

C++内存管理学习,第49张
5-中

C++内存管理学习,第50张
5-下

C++内存管理学习,第51张
6

C++内存管理学习,第52张
7

C++内存管理学习,第53张
8

C++内存管理学习,第54张
9
C++内存管理学习,第55张
G2.9 std::alloc 的使用

临时变量会被拷贝到容器申请得到的空间上

C++内存管理学习,第56张
G2.9 源码中值得讨论的点
  • 1处与4处: 建议写成两行,否则让人难以理解
  • 2处:值得学习,比较运算符右值放到左边
  • 3处:变量最好在使用时的上一行定义,这样可以防止一些bug的出现
  • 5处:是站在其他进程的角度考虑,才有它注释说的大灾难
  • 6处(5下面,忘记标注了):系统中没有使用free,当alloc失败的时候,会导致有很多空闲的内存仍然没有被程序使用,如上图中第十三步中的白色区块
C++内存管理学习,第57张
G4.9 可以统计使用多少次new和delete 以及申请多大空间的方法 (G2.9不支持)

重载global operaot new operator delete

C++内存管理学习,第58张
测试 使用标准alloc之后,性能的优化
C++内存管理学习,第59张
G2.9 移植到c的方法

malloc & free

VC6 & VC10内存分配一览

C++内存管理学习,第60张
VC6 内存分配

C++内存管理学习,第61张
VC10 内存分配

注: 左边是main函数调用堆栈,关于main函数调用前后做了啥,看这里哦
1016是因为 有8个字节是给cookie预留

Small Block Heap ——SBH开始

C++内存管理学习,第62张
SBH 之始

HeapCreate是windows系统分配内存的api,这里会分配10个header

C++内存管理学习,第63张
header 数据结构

bitvEntryHi和bitvEntryLo组成一个64位的bitmap
其他的两个指针会作为内存大表重要的一部分,具体可见下图 VC6内存分配源码-6
这一个header未来会管理1MB的内存

C++内存管理学习,第64张
sbh回顾 —— header结构
C++内存管理学习,第65张
VC6内存分配源码-1

这里是所有c++进程main函数的入口,所有的程序一进来就会分配32*8=256字节内存

C++内存管理学习,第66张
VC6内存分配源码-2

这里对要分配的256字节的内存空间,外层套了一个壳子(调整实际需要分配的内存空间的大小为256+48(36转化为16的倍数为)=304,16进制表示为0x130),用于存储调试用的信息(DEBUG模式专属)


C++内存管理学习,第67张
sbh回顾 —— 内存分配

少画一格,四个字节,用来做16倍数对齐

C++内存管理学习,第68张
VC6内存分配源码-3

0x131为cookie,本来为0x130,加1代表被使用了

C++内存管理学习,第69张
VC6内存分配源码-4

1016是因为 有8个字节是给cookie预留

C++内存管理学习,第70张
VC6内存分配源码-5

这里调整大小为16的倍数大小,同时这里的大小包含cookie(2*sizeof(int))

C++内存管理学习,第71张
VC6内存分配源码-6

最上面带两个点的图代表上图tagheader这个数据结构,这俩点就表示两个指针,一个指向group组用来管理所有的内存块,一个指向实际的虚拟内存地址空间
tagRegion中indGroupUse代表当前分配到第几个group
bitvGroupHi和bitvGroupLo组合成为32*64位,对应32个group
grpHeaderList为32个group,数据结构为TagGroup
TagGroup中cntEntries代表当前group分配了多少次内存,之后为64个TagListHead
TagListHead里面为指向内存区间的两个指针,连为双向链表
从而最终有64个链表,也就是32对双向链表,同一节点用来链接同样大小的内存区间

C++内存管理学习,第72张
VC6内存分配源码-7

1MB先分为8*32页,每页4k大小,8页为一组,被一个group管理
1MB内存的管理中心,分为32个group,从16字节到1k,1k以上的内存块都链接到最后一个grpHeaderList[31]

C++内存管理学习,第73张
VC6内存分配源码-8

0xffffff为-1,作为分隔符使用,减去这个空间之后,4096-8=4088个字节,但是为了16字节对齐,所以保留8个字节,这8个字节完全浪费掉了,只是为了对齐,剩下4080大小的空间待被分配使用

C++内存管理学习,第74张
VC6内存分配源码-9

响应第一个分配内存的请求,分配0x130字节空间,之后内存分布如上图所示
00000002标志位代表内存分配请求的来源,所以可以通过次标志位判断是否有内存泄漏,main函数运行完,归还内存时如果检查到有此位为00000001的那么就是发生了内存泄漏

C++内存管理学习,第75张
VC6内存第一次分配

C++内存管理学习,第76张
VC6内存第一次分配

C++内存管理学习,第77张
VC6内存第三次分配

C++内存管理学习,第78张
VC6内存第十五次分配

C++内存管理学习,第79张
VC6内存第十六次分配

C++内存管理学习,第80张
VC6内存第n次分配
C++内存管理学习,第81张
VC6内存第一次合并
C++内存管理学习,第82张
内存回收分析
C++内存管理学习,第83张
全回收后怎样1

通过一个count,也就是上面VC6内存分配源码-6 中taggroup数据结构内的cntEntries,这个字段来判断,申请一次内存这个值+1,释放一次内存这个值-1;(类似的设计在其他编译器的内存分配中同样出现)
全回收后内存回到VC6内存分配源码-7 中初始的状态

C++内存管理学习,第84张
全回收后会怎样2

一整块1MB内存被全回收后,被不会立刻被操作系统回收,而是等待下次被使用,直至有第二个全回收的1MB内存,才会回收,因此会至少保留一块1MB作为预留使用(使用指针__sbh_pHeaderDefer保存)

C++内存管理学习,第85张
全回收后的1MB内存回到初始状态
C++内存管理学习,第86张
VC6 堆内存状态分析的系统函数

这些系统函数可以分析sbh内存分配管理系统

C++内存管理学习,第87张
VC Malloc && GCC allocator

malloc速度并不慢,因为sbh很优秀
std::allocator存在的意义并不是malloc不够快,一部分原因是为了节约cookie,相当于节约内存

C++内存管理学习,第88张
图中每一层都有相对应的内存池的管理

为什么图中每一层都有内存管理,是不是冗余的,是不是浪费?
答案是浪费的,是冗余的,但是确实有必要,因为c是跨平台的,自己是完整的系统,同样c++也是有自己完整的系统,不依赖于自己的底层, 每一层都互相不依赖,不能预设其他层次有优秀的内存管理存在,可以保证自己内存管理是优秀的,性能是高的,因为每一层都使用自己优秀但是类似的内存池管理设计。

Loki Library 中内存管理实现的学习

C++内存管理学习,第89张
Loki Library

这是一个关于设计模式的库,但是不是很成熟,维护度不高,但是作者是《Modern c++ Design》的作者

C++内存管理学习,第90张
Loki 内存管理器的三个核心类

Loki 中的allocator的实现方式基于上述三个类,最底层的类是Chunk,暴露给用户的类是SmallObjAllocator,这三个类在一个.h和一个.cpp文件中非常精简,其中Chunk类是FixedAllocator类的inner类
其中使用的数据结构也如上图所示,其中最底层chunk 中pdata指向实际要管理的一块内存,firstAvailableBlock_用来表示接下里要供应的区块号的索引,blocksAvailable表示当前chunk中还有多少区块可以使用(相当于sbh中的count计数,用来判断当前chunk内存使用的状态,是全部归还了,还是全部被使用了)

C++内存管理学习,第91张
Loki allocator使用原理-1
C++内存管理学习,第92张
Loki allocator使用原理-2
C++内存管理学习,第93张
Loki allocator使用原理-3
C++内存管理学习,第94张
Loki allocator使用原理-4
C++内存管理学习,第95张
Loki allocator使用原理-5
C++内存管理学习,第96张
Loki allocator使用原理-6
C++内存管理学习,第97张
Loki allocator使用原理-7
C++内存管理学习,第98张
Chunk——Init

使用每一个block的第一个字节赋值的初始索引值,用来代替sbm中的链表

C++内存管理学习,第99张
Chunk——Allocator

这里只判断当前block是否还有内存可以分配,同时修改chunk类内数据成员的状态

C++内存管理学习,第100张
Chunk——Deallocator

归还的时候先判断当前内存是在哪个chunk,然后调用chunk的deallocator函数归还内存,

C++内存管理学习,第101张
FixedAllocator——Allocator&Deallocate

这里有两个特殊指针,作为标兵:allocChunk和deallocChunk;;
allocChunk用来表示下次优先考虑提供内存的chunk,因为默认会觉得上次这个chunk可以提供内存,那么下次大概率也可以
deallocChunk用来表示上次内存回收到这个chunk,那么下次大概率可能也会回收到这个chunk

C++内存管理学习,第102张
FixedAllocator——VicinityFind

临近查找法,其实看起来就是二路查找法,兵分两路查找

C++内存管理学习,第103张
FixedAllocator——DeAllocator

回收时同样会判断是否是全回收,同样,全回收之后会预留一个以供不时之需,不会还给操作系统
同时这里存在一个bug?(来找茬,反正我没找到)

C++内存管理学习,第104张
Loki-Allocator 总结

https://www.xamrdz.com/web/2cs1997592.html

相关文章: