長期運行的 C/C++ 程式若頻繁 malloc/new 小塊記憶體,堆會產生大量碎片,分配速度下降、記憶體利用率惡化。記憶體池透過預分配大塊、切分固定大小區塊徹底解決此問題。
// 區塊描述符
struct SMemoryChunk {
unsigned char* Data; // 指向實際資料區
size_t DataSize; // 從此區塊到池尾的剩餘位元組數
size_t UsedSize; // 實際使用大小(0 表示空閒)
bool IsAllocationChunk; // 是否為本次分配的首區塊
SMemoryChunk* Next; // 鏈表下一節點
};
// 介面
class IMemoryBlock {
public:
virtual ~IMemoryBlock() {}
virtual void* GetMemory(size_t size) = 0;
virtual void FreeMemory(void* ptr, size_t size) = 0;
};
// 記憶體池實現
class CMemoryPool : public IMemoryBlock {
public:
CMemoryPool(size_t initPoolSize = 1000,
size_t chunkSize = 128,
size_t minAlloc = 256,
bool fillPattern = false);
~CMemoryPool();
void* GetMemory(size_t size) override;
void FreeMemory(void* ptr, size_t size) override;
private:
// 核心私有方法
bool AllocateMemory(size_t size); // 向 OS 申請大塊並切分
unsigned int CalculateNeededChunks(size_t size); // 所需區塊數
size_t CalculateBestMemoryBlockSize(size_t req); // 對齊後的實際分配大小
bool LinkChunksToData(SMemoryChunk* chunks, unsigned int count, unsigned char* mem); // 建立鏈表
bool RecalcChunkMemorySize(SMemoryChunk* first, unsigned int count); // 更新 DataSize
SMemoryChunk* SetChunkDefaults(SMemoryChunk* c); // 初始化區塊描述符
SMemoryChunk* FindChunkSuitableToHoldMemory(size_t size); // 首次適配搜尋
SMemoryChunk* SkipChunks(SMemoryChunk* start, unsigned int n); // 跳過已用區塊
void SetMemoryChunkValues(SMemoryChunk* c, size_t used); // 標記已用
size_t MaxValue(size_t a, size_t b) const;
// 狀態
SMemoryChunk *m_first, *m_last, *m_cursor;
size_t m_totalSize, m_usedSize, m_freeSize, m_chunkSize;
unsigned int m_chunkCount, m_objectCount;
bool m_fillPattern;
size_t m_minAlloc;
};
GetMemorybestSize(chunkSize 的倍數)。m_cursor 開始首次適配搜尋 FindChunkSuitableToHoldMemory:DataSize >= bestSize 且 UsedSize == 0 → 找到。UsedSize 換算區塊數跳過(SkipChunks)。AllocateMemory),再搜尋。UsedSize = bestSize,更新統計,返回 chunk->Data。FreeMemory(原始碼中為空殼,實際需實現):
UsedSize = 0,更新統計,游標可回退至該區塊加速後續分配。AllocateMemoryn = ceil(req / chunkSize)。realSize = n * chunkSize(自動對齊)。malloc(realSize) 取得資料區,malloc(n * sizeof(SMemoryChunk)) 取得描述符陣列。LinkChunksToData 將描述符串成鏈表,每個 Data 指向資料區對應偏移。IsAllocationChunk = true,便於整體釋放追蹤。operator new
CMemoryPool* g_pool = nullptr;
class HeavyObject {
char buffer[256]; // 假設對象較大
public:
void* operator new(size_t sz) { return g_pool->GetMemory(sz); }
void operator delete(void* p, size_t sz) { g_pool->FreeMemory(p, sz); }
};
int main() {
g_pool = new CMemoryPool(1024*1024, 256); // 1MB 池,256B 區塊
HeavyObject* obj = new HeavyObject(); // 從池分配
delete obj; // 歸還池
delete g_pool; // 析構時檢查洩漏
return 0;
}
chunkSize(如 16/64/256/1024),按請求大小選擇池,減少內部碎片。| 適用場景 | 不適用場景 |
|---|---|
| 長期運行、高頻小塊分配(遊戲實體、網路封包、解析器節點) | 短生命程式、大塊不頻繁分配 |
| 嵌入式/實時系統,需確定性分配延遲 | 記憶體極度受限(池本身有描述符開銷) |
| 需內建洩漏偵測、堆轉儲診斷 | 大量使用標準庫容器(需自定義分配器配合) |
記憶體池是系統級程式設計師工具箱中的利器,正確使用能讓長期運行服務的記憶體行為可預測、可觀測、可控制。