话说一直想找一个别人写好的使用,可惜没什么人会拿这小东西发布,只好自写一个。
1.多级链表分配池
我不知道这种设计的具体学名是什么,这部分的内容也许你去看《STL源码分析》的有关章节更合适一些,这里我只能用我粗陋的语言描述一下。
内存池,完全可以从字面上理解为从池子里申请内存,释放的时候还给池子。
最简单的内存池应该是fix_pool吧,即每次分配出来的内存块大小是固定的。这种池子的管理结构是一个链表,链表的每一个节点为固定大小的内存块。分配的时候,直接返回链表的第一个节点,节点不足时,从系统申请大块内存分成多个节点加入链表;释放的时候更简单,将释放的内存加入链表头。
假设fix_pool的fix size = 128,那么内存池可以为128byte以下的任意大小的请求进行分配,但是这样做相当浪费呢,于是unfix_pool就在此基础上出现了。
由多个分配大小不同的fix_pool所组成的内存池就叫做多级链表分配池,我是这么定义的。
常规上会定义8,16,24,32,...,112,120,128这些分配大小,共16级。分配或者释放的时候,判断请求的大小在哪一级别上,用该级别的fix_pool链表进行分配或者释放。
2.泄漏检测
当所有的分配都经过你的手的时候,泄漏检测什么的再简单不过了。
找个地方把分配的东西记录下来,释放的时候把记录去掉。程序退出的时候还存在的分配记录就是泄漏了。
我个人选用的方法是给每一个分配请求多分配一些内存,用来记录分配的信息,并将这部分信息用双向链表串起来。释放的时候对释放的指针做一下指针偏移就可以找到信息记录并移出双向链表。
这个方法的开销是常数级的,不过无法处理重复删除的问题。
3.operater new
要把你的内存池应用到每一个角落,需要定义operator new和operator delete。
void* operator new(size_t) throw(std::bad_alloc);
void operator delete(void* p);
但是这还不够,谁也不想看到一堆泄漏信息而找不到泄漏的位置,因此还需要定义带附加参数的operator。
对于placement new而言,operator new[]和operator delete[]是必须的,无法省略。
void* operator new(size_t, const char* file, int line, const char* function);
void* operator new[](size_t, const char*, int, const char*);
void operator delete(void* p);
void operator delete[](void* p);
为了能用上新的operator,需要在头文件中重新定义new,并包含进每一个cpp文件。
//op_new.h
#define DEBUG_NEW new(__FILE__, __LINE__, __FUNCTION__)
#define new DEBUG_new
不过重定义new会和自行使用placement new的地方冲突,如stl容器库,这时候要undef new后才能编译冲突组件。
#undef new
#include <vector>
#include "op_new.h"
4.线程安全
我没听说过new/delete,malloc/free是线程不安全的,所以在内存池的allocate/deallocate接口处直接加了锁。
想降低开销的同学可以使用spin lock,而不是mutex。