C 手寫記憶體池的案例詳解

2022-09-24 19:57:08 字數 3622 閱讀 9266

使用new expression為類的多個例項分配動態記憶體時,cookie導致記憶體利用率可能不高,此時我們通過實現類的記憶體池來降低overhead。從不成熟到巧妙優化的記憶體池,得益於union的分時復用特性,記憶體利用率得到了提高。

在例項化某個類的物件時(在heap而不是stack中),若不使用array new,則每次例項化時都要呼叫一次記憶體分配函式,類的每個例項在記憶體中都有上下兩個cookie,從而降低了記憶體的利用率。然而,array new也有先天的缺陷,即只能呼叫預設無參建構函式,這對於很多沒有提供無參建構函式的類來說是不合適的。

因此,我們可以對於乙個沒有例項化的類第一次例項化時,先分配一大塊記憶體(記憶體池),這一大塊記憶體記程式設計客棧錄在類中,只有上下兩個cookie,能夠容納多個例項。後續例項化時,若記憶體池中還有剩餘記憶體,則不必申請記憶體分配,只在記憶體池中分配。記憶體**時,將例項所占用的記憶體**到記憶體池中。若記憶體池中無記憶體,則再申請分配大塊記憶體。

我們以鍊錶的形式組織記憶體池,記憶體池中每個乙個鍊錶是乙個小桶,這個桶中裝我們例項化的物件。

記憶體池鍊錶的頭結點記錄在類中,即以class staic變數的形式儲存。組織形式如下:

實現**如下:

#include

using namespace std;

class democlass

static void* operator new(size_t size);

static void operator delete(void *);

virtual ~democlass(){}

private:

democlass *next;

int data;

static democlass *freememheader;

static const size_t pool_size;

};democlass * democlass::freememheader = nullptr;

const size_t democlass::pool_size = 24;//設定記憶體池能容納24個democlass物件

void* democlass::operator new(size_t size)

freememheader[pool_size - 程式設計客棧1].next = nullptr;

}p = freememheader;//取記憶體池(鍊錶)的頭部,分配給要例項化的物件

cout << "info:從記憶體池中取了" << size << "位元組的記憶體。" << endl;

freememheader = freememheader -> next;//從記憶體池中刪去取出的那一小塊位址,即更新記憶體池

p -> next = nullptr;

return p;

}void democlass::operator delete(void* p)

測試**如下:

int main(int argc, char* ar**)

return 0;

}其結果如下:

可以看到每個democlass的例項大小為24位元組,記憶體池一次從作業系統中申請了576個位元組的記憶體,這些記憶體可以容納24個例項。上面顯示出了每個例項的記憶體位址,記憶體池中相鄰例項的記憶體首位址之差為24,即例項的大小,證明了乙個記憶體池的例項之間確實沒有cookie。

當記憶體池中記憶體用完後,又向作業系統申請了576個位元組的記憶體。

由此,只有每個記憶體池兩側有cookie,而記憶體池中的例項不存在cookie,相比於每次呼叫new expression例項化物件都有cookie,記憶體池的組織形式確實在形式上提高了記憶體利用率。

那麼,有什麼問題麼?

sizeof(democlass)等於24:

這樣乙個democlass的大小確實是24字pxjwjgh節。wait,what?

我們為了解決cookie帶來的記憶體浪費,引入了指標next,但卻又引入了8個位元組的overhead,脫褲子放屁,多此一舉?

這樣看來確實沒有達到要求,但至少為我們提供了一種思路,不是麼?

首先我們先回憶下c++ 中的union:

在任意時刻,聯合中只能有乙個資料成員可以有值。當給聯合中某個成員賦值之後,該聯合中的其它成員就變成未定義狀態了。

結合我們之前不成熟的記憶體池,我們發現,當記憶體池中的桶還沒有被分配給例項時,只有next域有用,而當桶被分配給例項後,next域就沒什麼用了;當桶被**時,資料域變無用而next指標又需要用到。這不正是union的特性麼?

看一下**實現:

#include

using namespace std;

class democlass

static void* operator new(size_t size);

static void operator delete(void *);

virtual ~democlass(){}

private:

struct demodata;

private:

static democlass *freememheader;

static const size_t pool_size;

union ;

};democlass * democlass::freememheader = nullptr;

const size_t democlass::pool_size = 24;//設定記憶體池能容納24個democlass物件

void* democlass::operator new(size_t size)

freememheader[pool_size - 1] = nullptr;

}p = freememheader;//取記憶體池(鍊錶)的頭部,分配給要例項化的物件

cout << "info:從記憶體池中取了" << size << "位元組的記憶體。" << endl;

freememheader = freememheader -> next;//從記憶體池中刪去取出的那一小塊位址,即更新記憶體池

p -> next = nullptr;

return p;

}void democlass::operator delete(void* p)

對比前一種實現**,只是建構函式、資料域和指標域的組織形式發生了變化:

測試**依舊:

int main(int argc, char* ar**)

return 0;

}結果:

可以看到每個democlass的例項大小為24位元組,乙個記憶體池的例項之間沒有cookie。

分析一下sizeof(democlass)等於24的緣由:

這樣乙個democlass的大小確實是24位元組。利用union的分時復用特性,我們消除了初步方案中指標帶來的脫褲子放屁效果。

細心的讀者可能會發現,前面的那兩種方案都有共同的小缺陷,即當程式一直例項化而不析構時,記憶體池會向作業系統申請多次大塊記憶體,而當這些物件一起**時,記憶體池中的剩餘桶數會遠大於設定的pool_size的大小,這個峰值多大取決於類例項化和**的時機。

另外,記憶體池中的記憶體暫時不會**給作業系統,峰值很大可能會對記憶體分配帶來一些影響,不過這卻不屬於記憶體洩漏。在以後的文章中,我們可能會討論一些效能更好的記憶體分配方案。

[1] effective c++ 3/e

[2] c++ primer 5/e

[3] 侯捷老師的記憶體管理課程

記憶體池 C 記憶體池

c c 下記憶體管理是讓幾乎每乙個程式設計師頭疼的問題,分配足夠的記憶體 追蹤記憶體的分配 在不需要的時候釋放記憶體 這個任務相當複雜。1.呼叫malloc new,系統需要根據 最先匹配 最優匹配 或其他演算法在記憶體空閒塊表中查詢一塊空閒記憶體,呼叫free delete,系統可能需要合併空閒記...

記憶體池 MemPool 技術詳解 經典記憶體池

url align center b 概述 b align 記憶體池 mempool 技術備受推崇。我用google搜尋了下,沒有找到比較詳細的原理性的文章,故此補充乙個。另外,補充了boost pool元件與經典mempool的差異。同時也描述了mempool在sgi stl stlport中的運...

記憶體池技術詳解

記憶體池 mempool 技術備受推崇。我用google搜尋了下,沒有找到比較詳細的原理性的文章,故此補充乙個。另外,補充了boost pool元件與經典mempool的差異。同時也描述了mempool在sgi stl stlport中的運用。經典的記憶體池 mempool 技術,是一種用於分配大量...