1. 程式人生 > >【c++自學】第六章 記憶體高階話題

【c++自學】第六章 記憶體高階話題

第1節  new、delete進一步認識

一、綜述與回顧:第一章第4節、第四章第2節

二、從new說起

int *p1 = new int;    //初值隨機
int *p2 = new int();    //初值給0

1、new物件時加括號與否的區別

int *p1 = new A;
int *p2 = new A();

1)若類A為空類:沒有區別

2)若類A中有成員變數:帶括號的初始化會把一些和成員變數有關的記憶體請零(不是全部)

3)若類A中有建構函式:m_i等成員變數的問題就交給構造函數了,帶不帶括號的效果一樣

2、new和delete都幹了啥(測試方法:除錯——視窗——反彙編)

new首先呼叫了operator new函式,這個函式中呼叫了malloc函式,然後呼叫了A::A()建構函式;

delete首先呼叫了A::~A()解構函式,然後呼叫了operator delete函式,這個函式中呼叫了free函式。

注:malloc和free這一層再往下走就不是跨平臺的了。

第2節  new細節探祕、過載類內operator new和operator delete

一、new記憶體分配細節探祕

通過程式碼測試,一段記憶體的回收影響的範圍會很大(比如new的時候編譯器會記錄要分配的位元組數供delete時候使用)

二、過載類中的operator new和operator delete(包括operator new[ ]和operator delete[ ])

class A {
public:
    A(){
    cout << "gou" << endl;
    }
    ~A(){
    cout << "xi" << endl;    
    }
    static void* operator new(size_t size);
    static void operator delete(void* phead);
    static void* operator new[](size_t size);
    static void operator delete[](void* phead);
};
void* A::operator new(size_t size) {
    A *point = (A*)malloc(size);
    return point;
}
void* A::operator new[](size_t size) {    
    //這裡傳進來的是7,其中每個物件1個位元組,再加上4個位元組用來存放陣列長度3
    A *point = (A*)malloc(size);
    return point;
}
void A::operator delete(void* phead){
    free(phead);
}
void A::operator delete[](void* phead) {
    free(phead);
}
void func() {
    A *p1 = new A();
    delete p1;
    A *p2 = new A[3]();
    //new物件的陣列時,建構函式和解構函式都呼叫3次,但是operator new和operator delete都呼叫1次
    delete[] p2;
}

第3節  記憶體池概念、程式碼實現詳細分析

一、記憶體池的概念和實現原理概述

記憶體池解決問題:減少malloc的次數,也就是減少對記憶體的浪費,而且比malloc要快一點。

實現原理:用malloc一次申請一大段記憶體,然後一點一點分配給使用者。

二、一個類的記憶體池例項

operator new圖示(每次new的時候返回tmplink給新指標,,m_FreePost指向下一個空的記憶體,如果用完了就再新建):

      

operator delete圖示(先連起來,再讓m_FreePost指向釋放後的記憶體地址):

程式碼如下 :

class A {
//#define MYMEMORYPOOL 1
public:
    A() {}
    ~A() {}
    static void* operator new(size_t size);
    static void operator delete(void* phead);
    static int m_iCount;
    static int m_iMallocCount;
private:
    A* next;
    static A* m_FreePosi;	//總是指向可以分配出去記憶體的首地址
    static int m_sTrunkCount;	//一次分配多少倍的記憶體
};
int A::m_iCount = 0;
int A::m_iMallocCount = 0;
A* A::m_FreePosi = nullptr;
int A::m_sTrunkCount = 5;

void* A::operator new(size_t size) {
    //這裡的size每次都是4個位元組,因為裡面有個next指標佔用4位元組
#ifdef MYMEMORYPOOL
    A *point = (A*)malloc(size);
    return point;
#endif
    //記憶體池核心實現程式碼
    A *tmp;
    if (m_FreePosi == nullptr) {
        //為空,我要申請記憶體
    	size_t realsize = m_sTrunkCount * size;
	m_FreePosi = reinterpret_cast<A*>(new char[realsize]);	
        //這個new呼叫的是系統的malloc,這個時候應該自動分了
	tmp = m_FreePosi;	
        //這樣tmp也是指向這一大塊記憶體的首地址
	//五小塊要連結起來
	for (; tmp != &m_FreePosi[m_sTrunkCount - 1]; ++tmp) {
	    cout << tmp << endl;
            tmp->next = tmp + 1;
	}
	tmp->next = nullptr;
	++m_iMallocCount;
    }
    //上述程式碼利用tmp連結了幾個記憶體塊
    //如果if不滿足,其實就是返回m_FreePosi,然後m_FreePosi往下走一個
    tmp = m_FreePosi;
    m_FreePosi = m_FreePosi->next;
    ++m_iCount;
    return tmp;
}

void A::operator delete(void* phead) {
#ifdef MYMEMORYPOOL
    free(phead);
#endif 
    (static_cast<A*>(phead))->next = m_FreePosi;
    m_FreePosi = static_cast<A*>(phead);
}

void func() {
    clock_t start, end;
    start = clock();
    for (int i = 0; i < 15; i++) {
	A *pa = new A();
	/*cout << pa << endl;*/
    }
    end = clock();
    cout << "申請總記憶體次數:" << A::m_iCount << endl;
    cout << "呼叫malloc次數:" << A::m_iMallocCount << endl;
    cout << "用時:" << end - start << endl;
}

執行結果(每一次分配的 最後一個為空,所以沒有顯示):

三、記憶體池程式碼後續說明

delete很麻煩,所有釋放的記憶體一直在記憶體池手裡,不會歸還給作業系統!

第4節  嵌入式指標概念及範例、記憶體池改進版

一、嵌入式指標(embedded pointer)

一般應用在記憶體池相關的程式碼中,使用前提是sizeof(A)>4

工作原理:借用A物件所佔用記憶體的前4個位元組,用來鏈住空閒的記憶體塊。

class TestEP {
public:
    int m_i;
    int m_j;
public:
    struct obj {
        //嵌入式指標,指向一個obj結構物件
        struct obj* next;
    };
};
void func() {
    TestEP mytest;
    cout << sizeof(mytest) << endl;
    TestEP::obj *ptemp;
    ptemp = (TestEP::obj*)&mytest;    //ptemp指向mytest物件首地址
    //ptemp和ptemp->next是一個地址	
    ptemp->next = nullptr;
    //這裡的nullptr換成下一個記憶體的首地址,鏈起來就是第三節的內容了
}

二、記憶體池程式碼的改進:單獨為記憶體技術寫一個類!

第5節  過載全域性new、delete、定位new及過載等

一、過載全域性new、delete(一般都在類裡)

void * operator new(size_t size) {
    return malloc(size);
}
void * operator new[](size_t size) {
    return malloc(size);
}
void operator delete(void *phead){
    free(phead);
}
void operator delete[](void *phead) {
    free(phead);
}

二、定位new(placement new) 沒有placement delete

功能:在已經分配的原始記憶體中初始化一個物件(呼叫建構函式)

格式:類名 *指標變數名 = new (記憶體首地址) 類名();

class A {
public:
    int m_a;
    A() {
	cout << "建構函式A::A()" << endl;
    }
    A(int tmp):m_a(tmp){
        cout << "建構函式A::A(int)" << endl;
    }
    ~A() {
        cout << "解構函式A::~A()" << endl;
    }
    void* operator new(size_t size,void* phead) {
        return phead;
    }
};
void func() {
    void* p = (void*)new char[sizeof(A)];
    A* test1 = new(p) A();	//呼叫無參建構函式,並不分配記憶體
    void* p2 = (void*)new char[sizeof(A)];
    A* test2 = new(p2) A(12);
    
    /*delete test1;
    delete test2;*/
    
    test1->~A();    //可以手工呼叫解構函式
    test2->~A();
    delete (void*)test1;
    delete[](void*)test2;
}

placement new的過載:多個引數但是不分配記憶體

三、多種版本的operator new過載

A *pa = new(123,456) A();    //第一個引數必須為size,這兩個引數分別為第二、三個引數