C++内存管理学习

经典图片

经典图片

1. 申请内存的方式

四种申请内存的方式
四种申请内存的方式示例
new

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


new handle的使用场景
placement new
std::string 对于operator new的使用

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

delete

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

array new & delete

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

new、 operator new、::operateor new, malloc 之间的关系

new 解析重载

operator重载示例
内存布局 & cookie

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

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

VC6.0 malloc

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

内存池版本1的实现:

间隔减少证明确实cookie没有了

内存池版本2的实现:

内存池版本3的实现

内存池版本3的实现

内存池版本4的实现

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


标准库的allator实现

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

VC6.0 标准库allocator的实现
BC5 标准库allocator的实现
GNU2.9 标准allocator的实现

GNU2.9 分配器的实现有多个版本
实际GNU2.9 是标准库使用的alloc

GNU4.9 标准allocator的实现

GNU4.9 不同版本分配器的测试

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

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使用,剩下一半作为备用池子供下次使用
对应每个节点的空闲链表的方式

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

std::alloc运行顺序

第一步

第二步

第三步

第四步

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

第五步

第六步

第七步

第八步

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

第九步

第十步

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

第十一步

第十二步

第十三步-上

第十三步-下

std::alloc源码分析

1

2

3

4

5-上

5-中

5-下

6

7

8

9
G2.9 std::alloc 的使用

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

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

重载global operaot new operator delete

测试 使用标准alloc之后,性能的优化
G2.9 移植到c的方法

malloc & free

VC6 & VC10内存分配一览

VC6 内存分配

VC10 内存分配

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

Small Block Heap ——SBH开始

SBH 之始

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

header 数据结构

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

sbh回顾 —— header结构

VC6内存分配源码-1

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

VC6内存分配源码-2

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


sbh回顾 —— 内存分配

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

VC6内存分配源码-3

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

VC6内存分配源码-4

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

VC6内存分配源码-5

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

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对双向链表,同一节点用来链接同样大小的内存区间

VC6内存分配源码-7

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

VC6内存分配源码-8

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

VC6内存分配源码-9

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

VC6内存第一次分配

VC6内存第一次分配

VC6内存第三次分配

VC6内存第十五次分配

VC6内存第十六次分配

VC6内存第n次分配
VC6内存第一次合并
内存回收分析
全回收后怎样1

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

全回收后会怎样2

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

全回收后的1MB内存回到初始状态
VC6 堆内存状态分析的系统函数

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

VC Malloc && GCC allocator

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

图中每一层都有相对应的内存池的管理

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

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

Loki Library

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

Loki 内存管理器的三个核心类

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

Loki allocator使用原理-1
Loki allocator使用原理-2
Loki allocator使用原理-3
Loki allocator使用原理-4
Loki allocator使用原理-5
Loki allocator使用原理-6
Loki allocator使用原理-7
Chunk——Init

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

Chunk——Allocator

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

Chunk——Deallocator

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

FixedAllocator——Allocator&Deallocate

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

FixedAllocator——VicinityFind

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

FixedAllocator——DeAllocator

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

Loki-Allocator 总结
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容