内存管理--简单内存池的实现
内存管理–简单内存池的实现
内存池的作用
减少空间占用
使用 malloc 分配内存时 , malloc 会多分配一部分空间作为 cookie
cookie 的作用是记录分配的内存大小等信息, 方便 free 回收
C++中的::operator new(全局中的 new)和::operator free,其内部都是调用 malloc 或者 free
使用内存池, 可以让多次 new 申请的内存, 只需要分配 malloc 内存池时的 cookie 就可以了, 大大减少了 cookie 导致的内存浪费
减少频繁分配内存时的开销
自己实现内存池, 一次性分配较多的空间, 也就意味这多次 new 申请内存时, 只需要一次调用 malloc 就可,减少了 malloc 的开销
同时 delete 时,将内存返回内存池, 也能提高内存的复用
内存池 1.0
- 让类内含一个指针,在分配的时候方便链表使用
- 重载 new 操作符和 delete 操作符
- 含有静态成员内存池大小和当前头指针
[1.0]实现细节(易踩坑)
类内创建的静态成员变量需要在类外初始化
初始化方式例如
int dog::num = 0;重载 new 和 delete 的时候返回值类型和参数类型有要求
new : 返回值类型必须是*void**, 第一个参数必须是**size_t**
delete: 返回值类型必须是**void, 第一个参数必须是*void**,第二个参数必须是size_t**
重载 new 和 delete 都需要是静态方法(因为使用该方法的时候 , 成员还未初始化) 但编译器默认为静态,加不加无所谓
申请内存池的时候,需要**转换成类指针类型** , 转换会取其前几位地址
切块时**利用指针++会增加一个类型的大小**的特性,来填入链表
到末尾时要将最后节点的 next 置为 nullptr!!!
否则当内存池为空时 , 继续进入下一个指针造成栈溢出(因为是通过判断指针是否为空来确定内存池是否为空)
delete 重载时 , 也需要注意转换成类的类型,否则**可能会造成回收的内存大小出错**
for 循环串成链表时**条件中
cur != &header[MAX_BLOCK - 1]要记得使用&取地址**因为 cur 为指针,而 header[MAX_BLOCK - 1]为最后一个成员的内容
[1.0]成品代码
1 | class testMryCrl_1 { |
[1.0]缺点分析
类内维护一个指针,占用较大内存
一般实际操作中 , 类的大小都不会很大 , 维护一个指针 , 则会造成内存浪费
尤其在类的内存占用比较小的时候, 效果尤其明显
代码实现较为复杂,代码复用性不高
每当有一个类需要使用该内存池时,都需要类声明的时候写上重载的部分, 违背软件工程的思想
当不断进行新的内存池扩充时,最后会**导致内存池的容量越来越大**
初步想法是类似于哈希表中对哈希碰撞的处理 : 当内存池的数目超过一定大小,则全部回收,重新申请内存池
暂时不考虑实现
内存池 2.0
使用**union 联合体**来实现内存的复用性
因为考虑到指针只有在 new 分配内存时才会有作用,与 data 部分不会同时使用
union 联合体
一个结构存在多种身份,但是某一时刻下,只能会使用一种身份时, 为了节省内存而产生的结构
又称共用体, 其 内存占用以 union 中定义的变量最大者为准
联合体声明时,内部变量声明需要使用
;无名联合体和有名称的联合体表示含义不同
无名联合体可以视为对成员变量的声明
使用时可以直接使用声明的变量(同一时刻只能使用联合体内的一个成员,其他成员会视为未初始化)
有名称的联合体可以视为结构的声明
使用时需要初始化结构, 将该联合体视为一种类型进行声明
初始化其中一个成员, 其他成员则视为未初始化状态
本质是公用一个地址空间
[2.0]实现细节
- 如果有多个成员变量将内存池 1.0 中的成员变量,整合成一个结构体
- union 中定义该类的指针和数据的结构体
[2.0]成品代码
1 | class testMryCrl_2 { |
[2.0]缺点分析
- 代码复用性不高
- 回收机制有待优化
内存池 3.0
通过面向对象思想 , 将内存分配的动作封装成类,加以复用
使用时,让类内内含一个静态的分配器对象
[3.0]实现细节
了解指针真正的含义,理解**为什么可以在另一个类内定义链表指针**
指针的指向的是该内存的第一个字节, 然后通过该指针的类型来确定需要取多少字节作为数据
**指针类型不同**的情况下 , 如何实现链表的切割和初始化
将指针指针转换成 char*类型, 因为 char 的大小为 1 字节
malloc 分配内存的时候,怎么**确定分配内存的大小**
利用使用 new 和 delete 时, 编译器自动传入的 sizet 参数**(new 操作的对象的大小, 相当于 sizeof(T))_**
使用时, 需要确保类的大小不小于一个指针的大小!!!
链表操作时,需要转换成
obj*的类型,而指针的大小为 4 字节, 所以需要类的大小不小于一个指针的大小
[3.0]成品代码
1 | class myAlloc { |
[3.0]缺点分析
- 不同类需要使用该内存池的时候, 需要定义的内容其实是固定的
- 回收内存机制有待优化
- 需要确保使用该分配器的类的大小不小于一个指针的大小!!!
内存池 4.0
- 通过使用宏定义, 将需要重复的部分封装成一个宏,提高代码可读性
[4.0]实现细节
- 使用宏定义, 换行需要使用\
- 类外声明时,通过宏获得类型
- 将 static 方法声明称 protected
[4.0]成品代码
1 | class myAlloc { |



