C++内存管理学习:
内存池。
1.为什么要内存池:
每malloc一次不仅需要调用brk/mmap系统调用函数,还会产生上下两个用来标识一块被使用的内存的cookie。如果申请的内存只有8字节膨胀率将会达到百分之100。若分配100万次内存就会有800万字节的cookie。内存池的作用就是减少malloc的开销、减少浪费的cookie(宗旨就是快、省)。
2.内存池原理:
简介内存池:顾名思义就是用户自己管理一大块内存池,每次使用前先看池中是否有空余,有的话直接拿出,没有的话才使用operator new()向内核要一块内存。
首先需要明白c++ new这个不可重载的运算符的性质:
一.new 中做了三件事:
1.operator new :分配内存(可重载,若使用标准分配器其实就是调用malloc并做了返回值判断)。
2.Static_cast:将operator new 返回的void*转为左值对象类型。
3.Constructor:调用构造函数。
4.(提一下)placemenet new 由于用户无法直接调用构造函数(编译器会报错),c++中提供了placement new 这种方式来构造一块分配好的内存。
想要实现内存池显然只能通过重载operator new
在operator new 中每次要一大块内存(GNU中是不超过所需对象的20倍,且每新开辟一块内存时向内核索要20x2倍 该对象的大小 + 已分配/16)
二.如何管理一块内存池:
使用嵌入式指针(union)来实现内存池的管理,union这个关键字指明它的内存大小为union中最大的对象类型的大小。
内存池的使用分为2步:第一步分配一大块内存,第二步:从内存池中取出一块内存并调构造函数构造这块内存,此时该内存方可被用户使用。也就是说在取出前我们可以借用这些未被使用的内存来实现对内存池的管理。
一般实现方式:利用union使用每个该对象内存的前4个字节(一个指针),利用这个指针将内存池串联起来(单向链表)。为何要这样做:若有内存被释放,只需将此内存插入链表尾即可。
三.我实现的简易内存池:
四.内存池是如何处理内存碎片的:
GNU中_pool_alloc的设计:
简介_pool_alloc设计:这个构造器只接受8~128字节大小的对象构造(第一个篮子8字节、第二个篮子负责16字节。。。以此类推。若不足则补足,如95此时对齐为96,8的整数倍)。
如图所示当出现内存碎片时:
内存池中只剩24字节了下次我们要申请的对象为32字节,此时内存池一个该对象的内存都无法提供,此时产生内存碎片。
解决方法:将此24字节插入第三个篮子中(负责24字节的篮子),这样内存碎片将不复存在(由于最小单位为8字节,所以内存碎片的大小必为8的倍数)。
五.总结
左右值引用、写入时拷贝…这些都是c++为了追求效率而引入的特性。这些东西都和内存挂钩,像c/c++这种自由(随意操控内存)的语言内存管理是不可绕过的坎,虽然很多编译器都自带了内存管理的类(malloc也是)但是深入源码去看看这些优秀的内存管理库也是必要的,只有了解这些好东西的设计才能做到活现活用、心中自有丘壑。注:此篇为c++内存管理的入门篇。