話說一直想找一個別人寫好的使用,可惜沒什么人會拿這小東西發(fā)布,只好自寫一個。
1.多級鏈表分配池
我不知道這種設(shè)計的具體學(xué)名是什么,這部分的內(nèi)容也許你去看《STL源碼分析》的有關(guān)章節(jié)更合適一些,這里我只能用我粗陋的語言描述一下。
內(nèi)存池,完全可以從字面上理解為從池子里申請內(nèi)存,釋放的時候還給池子。
最簡單的內(nèi)存池應(yīng)該是fix_pool吧,即每次分配出來的內(nèi)存塊大小是固定的。這種池子的管理結(jié)構(gòu)是一個鏈表,鏈表的每一個節(jié)點為固定大小的內(nèi)存塊。分配的時候,直接返回鏈表的第一個節(jié)點,節(jié)點不足時,從系統(tǒng)申請大塊內(nèi)存分成多個節(jié)點加入鏈表;釋放的時候更簡單,將釋放的內(nèi)存加入鏈表頭。
假設(shè)fix_pool的fix size = 128,那么內(nèi)存池可以為128byte以下的任意大小的請求進行分配,但是這樣做相當(dāng)浪費呢,于是unfix_pool就在此基礎(chǔ)上出現(xiàn)了。
由多個分配大小不同的fix_pool所組成的內(nèi)存池就叫做多級鏈表分配池,我是這么定義的。
常規(guī)上會定義8,16,24,32,...,112,120,128這些分配大小,共16級。分配或者釋放的時候,判斷請求的大小在哪一級別上,用該級別的fix_pool鏈表進行分配或者釋放。
2.泄漏檢測
當(dāng)所有的分配都經(jīng)過你的手的時候,泄漏檢測什么的再簡單不過了。
找個地方把分配的東西記錄下來,釋放的時候把記錄去掉。程序退出的時候還存在的分配記錄就是泄漏了。
我個人選用的方法是給每一個分配請求多分配一些內(nèi)存,用來記錄分配的信息,并將這部分信息用雙向鏈表串起來。釋放的時候?qū)︶尫诺闹羔樧鲆幌轮羔樒凭涂梢哉业叫畔⒂涗洸⒁瞥鲭p向鏈表。
這個方法的開銷是常數(shù)級的,不過無法處理重復(fù)刪除的問題。
3.operater new
要把你的內(nèi)存池應(yīng)用到每一個角落,需要定義operator new和operator delete。
void* operator new(size_t) throw(std::bad_alloc);
void operator delete(void* p);
但是這還不夠,誰也不想看到一堆泄漏信息而找不到泄漏的位置,因此還需要定義帶附加參數(shù)的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的地方?jīng)_突,如stl容器庫,這時候要undef new后才能編譯沖突組件。
#undef new
#include <vector>
#include "op_new.h"
4.線程安全
我沒聽說過new/delete,malloc/free是線程不安全的,所以在內(nèi)存池的allocate/deallocate接口處直接加了鎖。
想降低開銷的同學(xué)可以使用spin lock,而不是mutex。