【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,這兩個引數分別為第二、三個引數