目录
Part4基本构件之一newdelete expression
Part16-补充:new handler,=default ,=delete
Part1概述
*malloc是memory allocation的意思~~
Part2内存分配的每一层面
在初期,内存技术不行,因此对内存锱铢必较!
当想要申请内存时:
最高阶的是使用STL中的内存分配器,它实际上是通过new,new[],new()这些较为低阶的函数实现的。但终归会回到malloc和free这两种函数上来。最低阶的是操作系统的内存分配函数
对内存分配的工具概览:
Part3四个层面的基本用法
对于第四种方式还不是很理解:
Part4基本构件之一newdelete expression
new:
1.申请一段指定类大小的空间(new->operator new->malloc)
2.转化为对应类类型
3.调用类构造函数
在operator new的源码中,有个std::nothrow_t& _THROW0()参数,表示这个函数不抛异常,取而代之的是返回一个空指针,用户通过判断是否为空指针来判断是否分配成功。
delete:
1.调用析构函数
2.释放内存 delete->operator delete->free
Pat7Array new
cookie中记录着分配空间的长度,以便free知道释放多少
array new是分配一个对象数组,通常容易犯得一个错误是在delete的时候忘记在delete后面加[]导致内存泄漏的问题。正如上图所说的,对于类中没有指针的类,不加[]可能问题不大,因为没有指针的类析构函数本来也就没有什么大的作用;但是,如果有指针,忘记写[],那么delete只会触发一次析构函数,delete掉一个指针指向的内存,其他指针指向的内存就会泄露(不是array本身发生泄露)。如上图的psa析构,str2和str3指向的地址会发生内存泄漏(析构的顺序依编译器而定)。
下面两张图分别表示有指针和没指针的对象数组分配内存的区别
delete pi并不是必要的
但如果创建的是对象的话(demo是有3个int的对象)
对于分配一个对象数组,他会把数组的大小也放到存放对象数组的内存块的开头,free时就会看到这个3,知道要调用3次析构函数。如果在delete内存的时候不加[ ],编译器会把他当成一个无指针的对象来析构,那么,他碰到存放数组大小的那块内存的时候,布局就乱掉了
Part8 replacement new
用array new调用的是类的默认构造函数,还需要对数组中的对象进行真正的构造。
这就需要replacement new来允许我们将对象分配在已经构建的内存中
它不会进行内存分配,而是调用重载的operator new,用于返回已经分配好的内存,转型、调用构造函数
(buf指针正是代表那块allocated memory)
replacement new 等同于执行构造函数
Part9重载
本节课所讲的内容就是讨论怎么把路线2转为路线1
全局版本的重载:(但是影响会很大)
更常用的是在类中进行重载:
重载这些函数的目的是什么?——为了接管内存分配的工作
接管了它很有用!例如,可以做一个内存池
如果写了这些,就会绕过我们自己重载的函数,调用global version
Part12-per-class allocator
↑这就是一个小型的内存池(不过只能在这个类中使用)
让我们来使用一下这个写好的类:
在刚刚的例子中,多了一根next指针,现在我们来处理一下这个问题
(这时还是有一个遗憾,没有把这些分配的空间还给操作系统)
Part14 Static allocator
前面写了next版本和优化掉next的版本,现在是第三个版本——怎么避免给每个需要内存管理的类都写一个差不多的重载呢?
运行查看:因为我们的chunk=5,所以每次是一下分配5块内存
因此每5个是连续的,其他一般不会是连在一起滴
还有一种实现方式,将黄色的内容定义成宏(技术思想上并没有改进,只是一个偷懒工作)
对于3,4版本,已经有了标准库的雏形
Part16-补充:new handler,=default ,=delete
还记得之前operator new 里如果没有成功分配内存的callnewh()函数吗,ta就会调用上面的东西
使用方法:
当operator new没有能力为你分配出你所申请的memory,会抛出一个bad_alloc的异常。抛出异常之前会先(不止一次)调用一个可由用户指定的handler
_______________________________________
=default 使用默认版本
=delete 这个成员函数不能再被调用
——————————自此进入C++内存管理第二讲标准库allocator—————————
Part17 VC6 malloc()
ch代表16进制(想申请12个字节)
pad是为了将其填补到16的倍数
free时有一些机制,能把这一块还回去
Part18 VC6标准分配器之实现
(VC6所附的标准库中的allocator)
在右侧可以看到,几种容器的第二个参数都是ta!
Part19 BC5标准分配器之实现
Part20 G2.9标准分配器之实现
同样,没有任何特殊设计,和VC6,BC5一样。这个文件中已经说明,这个分配器没有用,容器的分配器不使用它!
Part22 G4.9 poll alloc用例
我们发现这个_gnu_cxx::pool_alloc真的去掉了cookie!
part23 G2.9的标准分配器
把ta放在最后讲是因为老师认为ta设计的最好!
ta和GNU4.9中的_pool_allocator相同
设计是内存池的思想。16个指针指向16条链表,每条链表负责分配不同大小的内存空间
从左往右每一条链表间隔8字节。理论上是这样。不过在代码实现中,是首先申请一个大的内存块,然后在大的内存块里进行切分(所以才出现了图中32,64字节空间在一块的现象)
每个链表会申请 大小*20*2字节的内存空间,最后这个2表示会申请2倍大小的内存用于战备。这些空间都没有cookie(cookie free)
如果申请的内存空间不在这个范围内,就不归这个来管理了
源码中的deallocate没有free操作(这可能是G4.9把ta替换回去的原因?)
<嵌入式指针>
在工业级的应用中,对象也确实都>=4
Part24-std_malloc运行一瞥
(RoundUp是一个追加量,计算方式是把目前的累积申请量除以16)
(先查pool中还有没有空间)
我们发现蓝色的战备池位置变来变去,实际上,有两根指针,他们之间就是标识的战备池
我们看到,系统中明明还有很多空间...
在了解这个过程之后,我们就可以去学习源码剖析~~
Part27-G2.9 std::alloc源码剖析
接下来,谈谈refill
拿一大块内存的工作交给chunk_alloc()函数
本函数代码如下:
中间失败的代码是把临近链表的空间试图给要求分配空间的