目錄
- 前言
- 1.C/C++記憶體分布
- 1.1虛擬記憶體分段
- 1.2理解一些概念
- 1.2.1堆疊幀向下增長
- 1.2.2堆向上生長
- 1.2.3堆疊和堆會碰撞嗎?
- 1.2.4關于const的說明
- 2.C語言中動態記憶體管理方式
- 2.1malloc/calloc/realloc和free
- 2.2 malloc/calloc/realloc的區別
- 3.C++記憶體管理方式
- 3.1 new/delete操作內置型別
- 3.1.1malloc和freeVSnew和delete
- 3.1.2 使用new動態申請的實體
- 3.2 new和delete操作自定義型別
- 4.operator new與operator delete函式
- 4.1 operator new與operator delete函式
- 4.1.1對比申請失敗處理方式
- 4.1.2 operator new和operator delete原理探尋
- 4.1.2.1 operator new實作原理
- 4.1.2.2 operator delete 實作原理
- 4.2 operator new與operator delete的類專屬多載
- 4.2.1理解為什么要專屬多載
- 4.2.2 operator new和operator delete 專屬多載的實作
- 5.new和delete的實作原理
- 5.1內置型別
- 5.2自定義型別
- 5.2.1 new的原理
- 5.2.2 delete的原理
- 5.2.3 new T[N]的原理
- 5.2.4 delete[]的原理
- 6.定位new運算式(placement-new)
- 6.1一個使用場景
- 6.2 使用格式
- 6.3 定位new的使用
- 7.關于記憶體的常見問題
- 7.1 malloc和realloc的區別
- 7.2 記憶體泄露
- 7.2.1 什么是記憶體泄露,記憶體泄露的危害是什么?
- 7.2.1.1思考:記憶體泄露是指標丟了還是記憶體丟了?
- 7.2.1.2 關于申請和釋放記憶體本質的思考
- 7.2.2 記憶體泄露的分類
- 7.2.2.1 堆記憶體泄漏(Heap leak)
- 7.2.2.2 系統資源泄漏
- 7.3 如何申請4G的空間
- 后記
前言
hello,大家好,今天我們來繼續分享C++的知識點,記憶體管理,歡迎大家繼續支持哦,閑言少敘,讓我們開始吧,
1.C/C++記憶體分布
1.1虛擬記憶體分段
一所學校,在設計的時候是有其規劃的,要有宿舍樓,要有辦公樓,要有教學樓,要有圖書館,要有體育館,要有食堂,要有禮堂……,不同的建筑發揮不同的作用,使校園整體功能齊全,設計合理,
記憶體也是如此,我們稱之為虛擬記憶體分段,

注意:
- 堆疊又叫堆疊,非靜態區域變數/函式引數/回傳值等等,堆疊是向下增長的,
- 記憶體映射段是高效的I/O映射方式,用于裝載一個共享的動態記憶體庫,用戶可使用系統介面創建共享共享記憶體,做行程間通信,
- 堆用于程式運行時動態記憶體分配,堆是可以上增長的,
- 資料段–存盤全域資料和靜態資料,
- 代碼段–可執行的代碼/只讀常量,
1.2理解一些概念
1.2.1堆疊幀向下增長
堆疊又叫堆疊,非靜態區域變數/函式引數/回傳值等等,堆疊是向下增長的,
那么,什么叫做向下增長呢?我們來看一個例子:
#include<iostream>
using namespace std;
void f2()
{
int b = 0;
cout <<"b:"<< &b << endl;
cout << endl;
cout << endl;
}
void f1()
{
int a = 0;
cout<<"a:" << &a << endl;
cout << endl;
cout << endl;
f2();
}
int main()
{
f1();
return 0;
}
在這段代碼中,函式呼叫的時候會在記憶體上建立堆疊幀,程式由main函式進入,接著呼叫f1,接著再呼叫f2.

我們來看一下輸出的,a和b的地址的結果:

我們發現,a的地址是比b的地址高的,也就是說在堆疊幀生長的程序中,地址是從高到低的,所以我們說,堆疊是向下生長的,
然后通過這個例子我們對區域變數的生命周期也可以有一個新的理解,出堆疊的時候堆疊會銷毀,區域變數也隨之消失,這就是區域變數的生命周期,
1.2.2堆向上生長
堆和堆疊類似,但堆是向上生長的,

但是,后申請的空間的地址一定會比先申請的大嗎?
當我們多申請幾次,來看一下:

這又是為什么呢?因為記憶體不斷申請和釋放,所以我們有可能申請到前面釋放過的空間,這就可能會導致這種情況的產生,所以堆是可以向上增長的,但不一定,
1.2.3堆疊和堆會碰撞嗎?
我們上面已經知道,堆疊和堆一個是向下生長,一個是向上生長,這么雙向生長難道不會碰撞嗎?

答案是不會的,
堆疊并不是一直可以向下走的,堆疊是有規定大小的,堆很大,但也是有上限的,所以會存在失敗的堆疊溢位和malloc失敗的情況,
1.2.4關于const的說明
我們說常量區是是儲存常量的,那么const定義的常量是存在常量區嗎?
答案是不是的,注意,const定義的是常變數,本質還是變數哦,記住,本質是變數,所以不在常量區,那么如果const定義在函式里面,就在堆疊區,如果定義成static就在靜態區,

2.C語言中動態記憶體管理方式
2.1malloc/calloc/realloc和free
void Test ()
{
int* p1 = (int*) malloc(sizeof(int));
free(p1);
int* p2 = (int*)calloc(4, sizeof (int));
int* p3 = (int*)realloc(p2, sizeof(int)*10);
free(p3 );
}
2.2 malloc/calloc/realloc的區別

3.C++記憶體管理方式
C語言記憶體管理方式在C++中可以繼續使用,但有些地方就無能為力而且使用起來比較麻煩,因此C++又提出了自己的記憶體管理方式:通過new和delete運算子進行動態記憶體管理
3.1 new/delete操作內置型別

3.1.1malloc和freeVSnew和delete
我們以申請10個int的陣列為例

申請和釋放單個元素的空間,使用new和delete運算子,申請和釋放連續的空間,使用new[]和delete[]
3.1.2 使用new動態申請的實體
void Test()
{
// 動態申請一個int型別的空間
int* ptr4 = new int;
// 動態申請一個int型別的空間并初始化為10
int* ptr5 = new int(10);
// 動態申請10個int型別的空間
int* ptr6 = new int[3];
delete ptr4;
delete ptr5;
delete[] ptr6;
}
3.2 new和delete操作自定義型別
#include<iostream>
using namespace std;
struct ListNode
{
ListNode *_next;
ListNode *_prev;
int _val;
ListNode(int val = 0)
:_next(nullptr), _prev(nullptr), _val(val)
{}
};
int main()
{
//C
struct ListNode *n1 = (struct ListNode *)malloc(sizeof(struct ListNode));
//C++
ListNode *n2 = new ListNode;
return 0;
}
我們來看看上面的代碼,我們分別開辟了n1和n2,他們會有什么不同嗎?我們來看除錯結果,

所以我們可以知道,malloc只是開空間,而new針對自定義型別時,是開空間加建構式初始化,
如果我們再使用free和delete,再來看看,當我們屏蔽掉new,

而當我們屏蔽掉malloc

我們發現,在delete的同時,也呼叫了解構式,而free并不能呼叫解構式,
于是,我們可以得出結論:申請自定義型別的空間時,new會呼叫建構式,delete會呼叫解構式,而malloc與free不會,

4.operator new與operator delete函式
4.1 operator new與operator delete函式
new和delete是用戶進行動態記憶體申請和釋放的運算子,operator new 和operator delete是系統提供的全域函式,new在底層呼叫operator new全域函式來申請空間,delete在底層通過operator delete全域函式來釋放空間,
4.1.1對比申請失敗處理方式
他們的用法和malloc和free一樣,都是在堆上申請釋放空間,但是失敗了的處理方式不同,malloc是回傳空指標,而operator new是拋例外
#include<iostream>
using namespace std;
struct ListNode
{
ListNode *_next;
ListNode *_prev;
int _val;
ListNode(int val = 0)
:_next(nullptr), _prev(nullptr), _val(val)
{}
~ListNode()
{
cout << "ListNode 析構" << endl;
}
};
int main()
{
struct ListNode *n1 = (struct ListNode *)malloc(sizeof(struct ListNode));
free(n1);
struct ListNode *n2 = (struct ListNode *)operator new(sizeof(struct ListNode));
operator delete (n2);
void *p3 = malloc(0x7fffffff);
if (p3 == NULL)
{
cout << "malloc fail" << endl;
}
try
{
void *p4 = operator new(0x7fffffff);
}
catch (exception &e)
{
cout << e.what() << endl;
}
return 0;
}

4.1.2 operator new和operator delete原理探尋
4.1.2.1 operator new實作原理
operator new:該函式實際通過malloc來申請空間,當malloc申請空間成功時直接回傳;申請空間失敗,嘗試執行空 間不足應對措施,如果改應對措施用戶設定了,則繼續申請,否則拋例外,
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
// try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{
// report no memory
// 如果申請記憶體失敗了,這里會拋出bad_alloc 型別例外
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
4.1.2.2 operator delete 實作原理
operator delete: 該函式最終是通過free來釋放空間的
void operator delete(void *pUserData)
{
_CrtMemBlockHeader * pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
if (pUserData == NULL)
return;
_mlock(_HEAP_LOCK); /* block other threads */
__TRY
/* get a pointer to memory block header */
pHead = pHdr(pUserData);
/* verify block type */
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg( pUserData, pHead->nBlockUse );
__FINALLY
_munlock(_HEAP_LOCK); /* release other threads */
__END_TRY_FINALLY
return;
}
4.2 operator new與operator delete的類專屬多載
4.2.1理解為什么要專屬多載
專屬多載,聽起來很高端的樣子,那么多載他們有什么好處呢?為了弄清楚這個問題,我們首先要先了解一個概念,池化技術,
什么叫池化技術,舉一個例子,嗯,作為一名貧窮的大學生,生活費的來源是家長,如果我們的生活費是隨花隨給的,早上你上食堂吃了兩根油條喝了一碗豆漿,要付款的時候你大手一揮和食堂大媽說等等,我要找我媽要錢,于是你給你媽媽打了一個電話,媽媽給你轉了5塊錢,你付了早飯錢(這一切建立在你早上能起床吃早飯的前提下,像我這種,笑死,早飯根本來不及,只能吃午飯了),嗯,然后午飯你從食堂買了一碗面,再給媽媽打電話要了10元,然后你付了午飯的錢……就這樣,你每花一次錢都需要找家長要一次,是不是很麻煩?還特別浪費你和媽媽之間的感情?
但是如果,哎,每個月月初,媽媽給你打1500元,對你說,孩兒啊,你這一個月生活費在這了哈,多退少不補哈,哎,這樣你就爽了,油條豆漿隨便刷啊,想買啥買啥,等到月底,呵呵,你懂,

池化技術就類似于給你一個月的生活費使你可以在需要花錢的時候自己就可以支付,而不再需要向媽媽要,在池化技術中,分為很多種類的池:

池中預存了我們需要的“生活費”,需要時直接自行支付,這樣就提高了程式的效率,專屬多載operator new和operator delete就是使用記憶體池進行申請和釋放空間,可以提高效率,

4.2.2 operator new和operator delete 專屬多載的實作
針對鏈表的節點ListNode通過多載類專屬 operator new/ operator delete,實作鏈表節點使用記憶體池申請和釋放記憶體,提高效率,
#include<iostream>
using namespace std;
struct ListNode
{
ListNode *_next;
ListNode *_prev;
int _val;
//類中專屬的多載
void* operator new(size_t n)
{
void* p = nullptr;
p = allocator<ListNode>().allocate(1);
cout << "memory pool allocate" << endl;
return p;
}
void operator delete(void* p)
{
allocator<ListNode>().deallocate((ListNode*)p, 1);
cout << "memory pool deallocate" << endl;
}
ListNode(int val = 0)
:_next(nullptr), _prev(nullptr), _val(val)
{}
~ListNode()
{
cout << "ListNode 析構" << endl;
}
};
int main()
{
ListNode* p = new ListNode(1);
delete p;
}
我們來對比一下使用多載和不使用多載的反匯編代碼

專屬多載還是很香的,畢竟誰不想要一筆可以自由支配的生活費呢?

5.new和delete的實作原理
5.1內置型別
如果申請的是內置型別的空間,new和malloc,delete和free基本類似,不同的地方是:new/delete申請和釋放的是單個元素的空間,new[]和delete[]申請的是連續空間,而且new在申請空間失敗時會拋例外,malloc會回傳NULL,
5.2自定義型別
5.2.1 new的原理
- 呼叫operator new函式申請空間
- 在申請的空間上執行建構式,完成物件的構造
5.2.2 delete的原理
- 在空間上執行解構式,完成物件中資源的清理作業
- 呼叫operator delete函式釋放物件的空間
5.2.3 new T[N]的原理
- 呼叫operator new[]函式,在operator new[]中實際呼叫operator new函式完成N個物件空間的申請,
- 在申請的空間上執行N次建構式,
5.2.4 delete[]的原理
- 在釋放的物件空間上執行N次解構式,完成N個物件中資源的清理
- 呼叫operator delete[]釋放空間,實際在operator delete[]中呼叫operator delete來釋放空間
6.定位new運算式(placement-new)
定位new運算式是在已分配的原始記憶體空間中呼叫建構式初始化一個物件,
6.1一個使用場景
#include<iostream>
using namespace std;
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A" << this << endl;
}
~A()
{
cout << "~A" << this << endl;
}
private:
int _a;
};
int main()
{
A*p = (A*)malloc(sizeof(A));
return 0;
}
我們看上面的代碼,我們申請了一塊和A大小相同的空間,那么我們如何來初始化它呢?
這種直接初始化的方法肯定行不通

我們說,呼叫建構式初始化啊,這樣可以嗎?

還是報錯的,為什么我們無法呼叫建構式呢?
我們來想一下,我們是malloc了一塊空間,空間的大小等于A而不是宣告了一個A的物件,這塊空間不是物件,然后將它強轉為A*,所以我們是無法呼叫建構式的,

那么我們如何初始化它呢?于是定位new橫空出世,
6.2 使用格式
new (place_address) type或者new (place_address) type(initializer-list)
place_address必須是一個指標,initializer-list 是型別的初始化串列
6.3 定位new的使用
定位new運算式在實際中一般是配合記憶體池使用,因為記憶體池分配出的記憶體沒有初始化,所以如果是自定義型別的物件,需要使用new的定義運算式進行顯示調建構式進行初始化,
#include<iostream>
using namespace std;
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A:" << _a<< endl;
}
~A()
{
cout << "~A:析構" << endl;
}
private:
int _a;
};
int main()
{
A*p = (A*)malloc(sizeof(A));
new(p)A;//顯示呼叫建構式
cout << endl;
new(p)A(3);//顯示呼叫建構式并賦予初始化的值
return 0;
}

7.關于記憶體的常見問題
7.1 malloc和realloc的區別
malloc/free和new/delete的共同點是:都是從堆上申請空間,并且需要用戶手動釋放,
不同的地方是:
- malloc和free是函式,new和delete是運算子
- malloc申請的空間不會初始化,new可以初始化
- malloc申請空間時,需要手動計算空間大小并傳遞,new只需在其后跟上空間的型別即可
- malloc的回傳值為void*, 在使用時必須強轉,new不需要,因為new后跟的是空間的型別
- malloc申請空間失敗時,回傳的是NULL,因此使用時必須判空,new不需要,但是new需要捕獲例外
- 申請自定義型別物件時,malloc/free只會開辟空間,不會呼叫建構式與解構式,而new在申請空間后會呼叫建構式完成物件的初始化,delete在釋放空間前會呼叫解構式完成空間中資源的清理
7.2 記憶體泄露
7.2.1 什么是記憶體泄露,記憶體泄露的危害是什么?
什么是記憶體泄漏:記憶體泄漏指因為疏忽或錯誤造成程式未能釋放已經不再使用的記憶體的情況,記憶體泄漏并不是指記憶體在物理上的消失,而是應用程式分配某段記憶體后,因為設計錯誤,失去了對該段記憶體的控制,因而造成了記憶體的浪費,
記憶體泄漏的危害:長期運行的程式出現記憶體泄漏,影響很大,如作業系統、后臺服務等等,出現記憶體泄漏會導致回應越來越慢,最終卡死,
void MemoryLeaks()
{
// 1.記憶體申請了忘記釋放
int* p1 = (int*)malloc(sizeof(int));
int* p2 = new int;
// 2.例外安全問題
int* p3 = new int[10];
Func(); // 這里Func函式拋例外導致 delete[] p3未執行,p3沒被釋放.
delete[] p3;
}
7.2.1.1思考:記憶體泄露是指標丟了還是記憶體丟了?
答案是指標丟了,記憶體是不會丟的,我們在堆上申請了一塊空間,我們拿著這塊空間的指標可以訪問這塊空間,由于我們的疏忽,我們弄丟了指標,導致無法知道指標,無法通過指標來釋放這塊空間,導致記憶體泄露,
就像你鑰匙丟了,進不去家門,你之所以進不去家門不是因為家丟了,而是鑰匙丟了,這里指標就相當于鑰匙,記憶體相當于家,記憶體是不會丟的,家永遠在那里,
所以記憶體泄露丟的是指標哦,

7.2.1.2 關于申請和釋放記憶體本質的思考
通過上面的問題,我們可以更加清晰的認識到:malloc和new申請一塊記憶體的本質是向系統索要一塊空間的使用權,free和delete的本質是當不使用這塊空間時將這塊空間歸還給系統,系統可以再分配給別人,
就像租房,你向主人租了這間房(堆上的一塊空間),房主把鑰匙(指標)交給你,你可以通過鑰匙訪問這間房,你不再租這間房了,要把鑰匙還給房東(釋放指標),房東識訓房子,他可以再把房子租給別人(再分配),
7.2.2 記憶體泄露的分類
C/C++程式中一般我們關心兩種方面的記憶體泄漏:
7.2.2.1 堆記憶體泄漏(Heap leak)
堆記憶體指的是程式執行中依據須要分配通過malloc / calloc / realloc / new等從堆中分配的一塊記憶體,用完后必須通過呼叫相應的 free或者delete 刪掉,假設程式的設計錯誤導致這部分記憶體沒有被釋放,那
么以后這部分空間將無法再被使用,就會產生Heap Leak,
7.2.2.2 系統資源泄漏
指程式使用系統分配的資源,比方套接字、檔案描述符、管道等沒有使用對應的函式釋放掉,導致系統資源的浪費,嚴重可導致系統效能減少,系統執行不穩定,
7.3 如何申請4G的空間
代碼如下:
#include <iostream>
using namespace std;
int main()
{
void* p = new char[0xfffffffful];
cout << "new:" << p << endl;
return 0;
}
后記
好的,這篇萬字博客就先肝到這里了,博主在這里對記憶體的一些知識做了比較全面的介紹,希望對大家有所幫助,其實關于記憶體管理啊,我覺得重點就在于合理申請,及時釋放,將有限的記憶體空間發揮出最大的價值,最近好多讓人凌亂的新聞,于是今天我們分享一首鮑勃·迪倫的《答案在風中飄揚》
How many roads must a man walk down
Before they call him a man?
How many seas must a white dove sail
Before she sleeps in the sand?
How many times must the cannon-balls fly
Before they ‘re forever banned?
The answer, my friend, is blowin’ in the wind
The answer is blowin’ in the wind.
How many years must a mountain exist
Before it is washed to the sea?
How many years can some people exist
Before they’re allowed to be free?
How many times can a man turn his head
Pretend that he just doesn’t see?
The answer my friend is blowin’ in the wind.
The answer is blowin’ in the wind
How many times must a man look up
Before he can see the sky?
How many ears must one man have
Before he can hear people cry?
How many deaths will it take
Till he knows that too many people have died?
The answer, my friend, is blowin’in the wind
The answer is blowin’in the wind.
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/294936.html
標籤:其他
