开源地址:Gitee :zhujilun : 完全注释版 Tiny STL
内存分配和释放
当我们 new 一个对象的时候,包含两个操作,首先 operator_new 分配内存,再调用构造函数构造对象内容
但我们 delete 一个对象的时候,同样先调用析构函数,再调用 operator_delete 释放内存
STL 将这个过程薄薄的包装了一下,实际上没优化
配置器定义于 中, 中又包含 <stl_alloc.h> <stl_construct.h>
内存配置由 alloc::allocate() 负责,内存释放由 alloc::deallocate() 负责
构造对象由 construct::construct() 负责,析构对象由 construct::destroy() 负责
内存分配中有几个问题:
1.可能会内存不足
2.小块内存会导致碎片
3.频繁申请/释放小块内存的性能问题
为了解决这些问题,STL 设计了双层配置器
第一层配置器直接使用 malloc()、free(),当配置区块超过128 bytes,认为足够大,直接用第一层配置器
第二层配置器使用内存池和自由链表
第一层配置器
第一层配置器{
直接 malloc(n) 申请内存
if 内存够{
直接申请成功
return
}
else{
调用内存不足处理函数oom_malloc(n)
if 内存不足时,释放内存的句柄 handler == 0{
throw std::bad_alloc 没设置的话系统也没法,没有内存了,也没法释放内存,只能给抛个异常
}
else{
while ret == 0 {
系统释放一块内存,释放出来了就有 ret = 1
否则一直循环
}
return
}
}
}
这样的malloc和free看似很方便,但正因为它的方便,通用,因此性能不高,在它的实现中还要配合操作系统才能完全完成
malloc(n){
不断搜索空闲内存块
if 找到了 {
return 把这块分配出去
} else {
brk:扩大堆区,获得更多的空闲内存,此时转入内核态
—————————————————————————————————————————
虚拟内存系统开始工作 内
额外扩大的这一部分仅仅是虚拟内存 核
并没有真的分配物理内存 态
—————————————————————————————————————————
return 找到一块合适的空闲内存
}
}
第二层配置器
内存池
首先介绍一下内存池
在内核中,有的地方要内存时,不能出现分配不了的情况,为了确保分配,与效率问题,需要内存池作为后备缓存,尽力保持出一个空间紧急使用
在真正使用内存前,先申请出一定数量的大小相等的(一大块)内存块,当有内存需求时,从内存块中分出几个,如果这些还不够,再申请新的内存
分配
首先有一个free_lists[16],每一位上记录大小为8、16、24、… 、128的区块数
并维护16个free_list,各自管理各自的区块
if free_list 中有空余内存{
for{
找到free_list[i]
break;
}
先把申请的字节数修改为8的倍数
return 该位置链表中第一个元素
头指针后移
}
else{
if 内存池 != 空{
if 内存池大小 > 申请的内存 * 8{
分配相应的内存
其中一个给用户return
其他的7个挂在free_list上
} else if 申请的内存 * 8 > 内存池大小 && 内存池大小 > 申请的内存 {
先凑合分配这几个
其中一个给用户return
剩下的挂链表
} else {
//内存池不够
if malloc(申请的一大块内存放进内存池) 成功{
一个给用户
剩下的挂链表
剩下的放内存池
}
else {
调用第一层配置器
}
}
}
}
回收
当申请释放一块内存时,这个内存被插入到对应的free_list中
总结
以上仅仅是STL的做法,具有一定的通用性,因此性能不会太好,在实际场景中肯定还是要自己写内存池的
代码实现
union obj {
//避免内存浪费的union
union obj *next; //指向下一个区块
char client[1]; //储存本块内存的首地址
};