欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C++手寫內(nèi)存池的案例詳解

 更新時間:2021年08月07日 12:58:03   作者:HickeyZhang  
這篇文章主要介紹了C++手寫內(nèi)存池的案例詳解,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下

引言

使用new expression為類的多個實例分配動態(tài)內(nèi)存時,cookie導致內(nèi)存利用率可能不高,此時我們通過實現(xiàn)類的內(nèi)存池來降低overhead。從不成熟到巧妙優(yōu)化的內(nèi)存池,得益于union的分時復用特性,內(nèi)存利用率得到了提高。

原因

在實例化某個類的對象時(在heap而不是stack中),若不使用array new,則每次實例化時都要調(diào)用一次內(nèi)存分配函數(shù),類的每個實例在內(nèi)存中都有上下兩個cookie,從而降低了內(nèi)存的利用率。然而,array new也有先天的缺陷,即只能調(diào)用默認無參構造函數(shù),這對于很多沒有提供無參構造函數(shù)的類來說是不合適的。

因此,我們可以對于一個沒有實例化的類第一次實例化時,先分配一大塊內(nèi)存(內(nèi)存池),這一大塊內(nèi)存記錄在類中,只有上下兩個cookie,能夠容納多個實例。后續(xù)實例化時,若內(nèi)存池中還有剩余內(nèi)存,則不必申請內(nèi)存分配,只在內(nèi)存池中分配。內(nèi)存回收時,將實例所占用的內(nèi)存回收到內(nèi)存池中。若內(nèi)存池中無內(nèi)存,則再申請分配大塊內(nèi)存。

脫褲子放屁方案

我們以鏈表的形式組織內(nèi)存池,內(nèi)存池中每個一個鏈表是一個小桶,這個桶中裝我們實例化的對象。

內(nèi)存池鏈表的頭結點記錄在類中,即以class staic變量的形式存儲。組織形式如下:

實現(xiàn)代碼如下:

#include <iostream>
using namespace std;
class DemoClass{
public:
    DemoClass() = default;
    DemoClass(int i):data(i){}
    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;//設定內(nèi)存池能容納24個DemoClass對象
void* DemoClass::operator new(size_t size){
    DemoClass* p;
    if(!freeMemHeader){//freeMemHeader為空,內(nèi)存池中無空間,分配內(nèi)存
        size_t pool_mem_bytes = size * POOL_SIZE;//內(nèi)存池的字節(jié)大小 = 每個實例的大?。ㄗ止?jié)數(shù))* 內(nèi)存池中能容納的最大實例數(shù)
        freeMemHeader = reinterpret_cast<DemoClass*>(new char[pool_mem_bytes]);//new char[]分配pool_mem_bytes個字節(jié),因為每個char占用1個字節(jié)
        cout << "Info:向操作系統(tǒng)申請了" << pool_mem_bytes << "字節(jié)的內(nèi)存。" << endl;
        for(int i = 0;i < POOL_SIZE - 1; ++i){//將內(nèi)存池中POOL_SIZE個小塊內(nèi)存,串起來。
            freeMemHeader[i].next = &freeMemHeader[i + 1];
        }
        freeMemHeader[POOL_SIZE - 1].next = nullptr;
    }
    p = freeMemHeader;//取內(nèi)存池(鏈表)的頭部,分配給要實例化的對象
    cout << "Info:從內(nèi)存池中取了" << size << "字節(jié)的內(nèi)存。" << endl;
    freeMemHeader = freeMemHeader -> next;//從內(nèi)存池中刪去取出的那一小塊地址,即更新內(nèi)存池
    p -> next = nullptr;
    return p;
}
void DemoClass::operator delete(void* p){
    DemoClass* tmp = (DemoClass*) p;
    tmp -> next = freeMemHeader;
    freeMemHeader = tmp;
}

測試代碼如下:

int main(int argc, char* argv[]){
    cout << "sizeof(DemoClass):" << sizeof(DemoClass) << endl;
    size_t N = 32;
    DemoClass* demos[N];
    for(int i = 0; i < N; ++i){
        demos[i] = new DemoClass(i);
        cout << "address of the ith demo:" << demos[i] << endl;
        cout << endl;
    }
    return 0;
}

其結果如下:

可以看到每個DemoClass的實例大小為24字節(jié),內(nèi)存池一次從操作系統(tǒng)中申請了576個字節(jié)的內(nèi)存,這些內(nèi)存可以容納24個實例。上面顯示出了每個實例的內(nèi)存地址,內(nèi)存池中相鄰實例的內(nèi)存首地址之差為24,即實例的大小,證明了一個內(nèi)存池的實例之間確實沒有cookie。

當內(nèi)存池中內(nèi)存用完后,又向操作系統(tǒng)申請了576個字節(jié)的內(nèi)存。

由此,只有每個內(nèi)存池兩側有cookie,而內(nèi)存池中的實例不存在cookie,相比于每次調(diào)用new expression實例化對象都有cookie,內(nèi)存池的組織形式確實在形式上提高了內(nèi)存利用率。

那么,有什么問題么?

sizeof(DemoClass)等于24

  1. int data數(shù)據(jù)域占4個字節(jié)
  2. 兩個構造函數(shù)一個析構函數(shù)各占4字節(jié),共12字節(jié)
  3. 額外的指針DemoClass*,在64位機器上,占8個字節(jié)

這樣一個DemoClass的大小確實是24字節(jié)。wait,what?

我們?yōu)榱私鉀Qcookie帶來的內(nèi)存浪費,引入了指針next,但卻又引入了8個字節(jié)的overhead,脫褲子放屁,多此一舉?

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

分時復用改進方案

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

在任意時刻,聯(lián)合中只能有一個數(shù)據(jù)成員可以有值。當給聯(lián)合中某個成員賦值之后,該聯(lián)合中的其它成員就變成未定義狀態(tài)了。

結合我們之前不成熟的內(nèi)存池,我們發(fā)現(xiàn),當內(nèi)存池中的桶還沒有被分配給實例時,只有next域有用,而當桶被分配給實例后,next域就沒什么用了;當桶被回收時,數(shù)據(jù)域變無用而next指針又需要用到。這不正是union的特性么?

看一下代碼實現(xiàn):

#include <iostream>
using namespace std;
class DemoClass{
public:
    DemoClass() = default;
    DemoClass(int i, double p){
        data.num = i;
        data.price = p;
    }
    static void* operator new(size_t size);
    static void operator delete(void *);
    virtual ~DemoClass(){}
private:
    struct DemoData{
        int num;
        double price;
    };
private:
    static DemoClass *freeMemHeader;
    static const size_t POOL_SIZE;
    union {
        DemoClass *next;
        DemoData data;
    };
    
};
DemoClass * DemoClass::freeMemHeader = nullptr;
const size_t DemoClass::POOL_SIZE = 24;//設定內(nèi)存池能容納24個DemoClass對象
void* DemoClass::operator new(size_t size){
    DemoClass* p;
    if(!freeMemHeader){//freeMemHeader為空,內(nèi)存池中無空間,分配內(nèi)存
        size_t pool_mem_bytes = size * POOL_SIZE;//內(nèi)存池的字節(jié)大小 = 每個實例的大?。ㄗ止?jié)數(shù))* 內(nèi)存池中能容納的最大實例數(shù)
        freeMemHeader = reinterpret_cast<DemoClass*>(new char[pool_mem_bytes]);//new char[]分配pool_mem_bytes個字節(jié),因為每個char占用1個字節(jié)
        cout << "Info:向操作系統(tǒng)申請了" << pool_mem_bytes << "字節(jié)的內(nèi)存。" << endl;
        for(int i = 0;i < POOL_SIZE - 1; ++i){//將內(nèi)存池中POOL_SIZE個小塊內(nèi)存,串起來。
            freeMemHeader[i].next = &freeMemHeader[i + 1];
        }
        freeMemHeader[POOL_SIZE - 1].next = nullptr;
    }
    p = freeMemHeader;//取內(nèi)存池(鏈表)的頭部,分配給要實例化的對象
    cout << "Info:從內(nèi)存池中取了" << size << "字節(jié)的內(nèi)存。" << endl;
    freeMemHeader = freeMemHeader -> next;//從內(nèi)存池中刪去取出的那一小塊地址,即更新內(nèi)存池
    p -> next = nullptr;
    return p;
}
void DemoClass::operator delete(void* p){
    DemoClass* tmp = (DemoClass*) p;
    tmp -> next = freeMemHeader;
    freeMemHeader = tmp;
}

對比前一種實現(xiàn)代碼,只是構造函數(shù)、數(shù)據(jù)域和指針域的組織形式發(fā)生了變化:

  • 由于數(shù)據(jù)域增加了price項,構造函數(shù)中也增加了對應的參數(shù)
  • 數(shù)據(jù)域被集成定義成一個類自定義struct類型
  • 數(shù)據(jù)域和指針域被組織為union

測試代碼依舊:

int main(int argc, char* argv[]){
    cout << "sizeof(DemoClass):" << sizeof(DemoClass) << endl;
    size_t N = 32;
    DemoClass* demos[N];
    for(int i = 0; i < N; ++i){
        demos[i] = new DemoClass(i, i * i);
        cout << "address of the " << i << "th demo:" << demos[i] << endl;
        cout << endl;
    }
    return 0;
}

結果:

可以看到每個DemoClass的實例大小為24字節(jié),一個內(nèi)存池的實例之間沒有cookie。

分析一下sizeof(DemoClass)等于24的緣由:

  • data數(shù)據(jù)域占12個字節(jié)(int 4字節(jié)、double 8字節(jié))。
  • 兩個構造函數(shù)一個析構函數(shù)各占4字節(jié),共12字節(jié)。
  • 指針DemoClass,在64位機器上,占8個字節(jié),但由于和數(shù)據(jù)域使用了union,data數(shù)據(jù)域12個字節(jié)中的前8個字節(jié)在適當?shù)臅r機被看作DemoClass,而不占用額外空間,消除了overhead。

這樣一個DemoClass的大小確實是24字節(jié)。利用union的分時復用特性,我們消除了初步方案中指針帶來的脫褲子放屁效果。

另外的思考

細心的讀者可能會發(fā)現(xiàn),前面的那兩種方案都有共同的小缺陷,即當程序一直實例化而不析構時,內(nèi)存池會向操作系統(tǒng)申請多次大塊內(nèi)存,而當這些對象一起回收時,內(nèi)存池中的剩余桶數(shù)會遠大于設定的POOL_SIZE的大小,這個峰值多大取決于類實例化和回收的時機。

另外,內(nèi)存池中的內(nèi)存暫時不會回收給操作系統(tǒng),峰值很大可能會對內(nèi)存分配帶來一些影響,不過這卻不屬于內(nèi)存泄漏。在以后的文章中,我們可能會討論一些性能更好的內(nèi)存分配方案。

參考資料

[1] Effective C++ 3/e

[2] C++ Primer 5/e

[3] 侯捷老師的內(nèi)存管理課程

到此這篇關于C++手寫內(nèi)存池的文章就介紹到這了,更多相關C++內(nèi)存池內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • C++類型轉(zhuǎn)換詳解

    C++類型轉(zhuǎn)換詳解

    類型轉(zhuǎn)換有c風格的,當然還有c++風格的。c風格的轉(zhuǎn)換的格式很簡單(TYPE)EXPRESSION,但是c風格的類型轉(zhuǎn)換有不少的缺點,有的時候用c風格的轉(zhuǎn)換是不合適的,因為它可以在任意類型之間轉(zhuǎn)換
    2021-10-10
  • C++中函數(shù)重載詳解

    C++中函數(shù)重載詳解

    大家好,本篇文章主要講的是C++中函數(shù)重載詳解,感興趣的同學趕快來看一看吧,對你有幫助的話記得收藏一下
    2022-02-02
  • C語言平衡二叉樹詳解

    C語言平衡二叉樹詳解

    這篇文章主要介紹了C語言平衡二叉樹的相關資料,需要的朋友可以參考下,小編覺得這篇文章寫的還不錯,希望能夠給你帶來幫助
    2021-11-11
  • C++類成員初始化的三種方式

    C++類成員初始化的三種方式

    如果靜態(tài)成員不滿足常量性,則不可以就地聲明,而且即使常量的靜態(tài)成員也只能是整型或者枚舉型才能就地初始化。而非靜態(tài)成員變量的初始化則必須在構造函數(shù)中進行。首先,先得了解一下C++支持哪幾種類成員初始化的方式,下面我們就來看看具體內(nèi)容吧
    2021-09-09
  • C++實現(xiàn)通訊錄系統(tǒng)項目實戰(zhàn)

    C++實現(xiàn)通訊錄系統(tǒng)項目實戰(zhàn)

    這篇文章主要為大家詳細介紹了C++實現(xiàn)通訊錄系統(tǒng)項目實戰(zhàn),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-06-06
  • C++ 右值語義相關總結

    C++ 右值語義相關總結

    這篇文章主要介紹了C++ 右值語義的的相關資料,幫助大家更好的理解和學習使用c++,感興趣的朋友可以了解下
    2021-02-02
  • 巧妙使用RAII中的ScopeExit

    巧妙使用RAII中的ScopeExit

    Resource Acquisition Is Initialization,資源獲取即初始化,將資源的生命周期與一個對象的生命周期綁定,這篇文章主要介紹了巧妙使用RAII中的ScopeExit,需要的朋友可以參考下
    2021-05-05
  • VS?Code?C++環(huán)境的搭建過程

    VS?Code?C++環(huán)境的搭建過程

    這篇文章主要介紹了VS?Code?C++環(huán)境的搭建,Somasegar 也告訴筆者這款編輯器也擁有對 Git 的開箱即用的支持,需要的朋友可以參考下
    2022-04-04
  • C++實現(xiàn)有向圖的鄰接表表示

    C++實現(xiàn)有向圖的鄰接表表示

    這篇文章主要為大家詳細介紹了C++實現(xiàn)有向圖的鄰接表表示,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-04-04
  • 數(shù)據(jù)結構課程設計-用棧實現(xiàn)表達式求值的方法詳解

    數(shù)據(jù)結構課程設計-用棧實現(xiàn)表達式求值的方法詳解

    本篇文章是對在c語言中用棧實現(xiàn)表達式求值的方法進行了詳細的分析介紹,需要的朋友參考下
    2013-05-05

最新評論