C++ 內(nèi)存管理原理分析
1.C/C++中程序內(nèi)存分布
C/C++中程序內(nèi)存區(qū)域大致劃分為:內(nèi)核空間(這部分用戶不能讀寫)、棧、內(nèi)存映射段、堆、數(shù)據(jù)段(存儲全局數(shù)據(jù)、靜態(tài)數(shù)據(jù))、代碼段(存儲可執(zhí)行代碼、只讀常量,又稱常量區(qū))。
1.1 內(nèi)存分布圖

1.2 小試牛刀
接下來看下如下代碼,思考下每一個變量分別在哪個內(nèi)存區(qū)域?
int globalVar = 1;
static int staticGlobalVar = 1;
void test()
{
static int staticVar = 1;
int localVar = 1;
int num1[10] = { 1,2,3,4 };
char char2[] = "abcd";
char *pchar3 = "abcd";//有的編譯器會報錯,需要用const char
int* ptr1 = (int*)malloc(sizeof(int) * 4);
int* ptr2 = (int*)calloc(4,sizeof(int));
int* ptr3 = (int*)realloc(ptr2,sizeof(int) * 4);
free(ptr1);
free(ptr2);
}
上述代碼段對應(yīng)變量區(qū)域劃分如下:

2.C語言部分的動態(tài)內(nèi)存管理方式
再來回顧一下之前C語言部分的動態(tài)內(nèi)存管理方式:malloc / calloc/ realloc和free
帶著兩個問題閱讀下述程序段:
1.malloc / calloc/ realloc的區(qū)別是什么?
2.最后需要free(p2)嗎?
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);
}
答:
1.calloc相當(dāng)于malloc+memset(0),即開空間+初始化。
2.realloc是對malloc/calloc的空間進行擴容,擴容之下又涉及到了咱們前面所講的原地擴容和異地擴容倆種情景:原地擴容p2和p3是一樣的,也有可能是異地擴容,那么p2指向的空間已經(jīng)被釋放了,所以兩種情況下我們都可以不需要處理p2。

3.C++內(nèi)存管理方式
總之就是C語言那套內(nèi)存管理方式相對麻煩,所以C++提出了自己的內(nèi)存管理方式:通過new和delete操作符進行動態(tài)內(nèi)存管理.
3.1new/delete操作內(nèi)置類型
1.開辟單個元素
開辟單個元素基本語法: type * ptr = new type(content); ,可以理解成動態(tài)申請一個type類型的空間并將其中內(nèi)容初始化為content,當(dāng)然,你也可以選擇不初始化。
釋放空間語法: delete name;
例:
int* a = new int(100); //動態(tài)申請一個int類型的空間并初始化為100 delete a;
2.開辟n個type類型的空間
開辟n個type類型基本語法: type* name = new type[n]
刪除的基本語法:delete[] name;
例:
int* ptr = new int[100];//動態(tài)申請100個int類型的空間 delete[] ptr; //注意哦!一定要匹配哦!不然必崩潰!
3.對于內(nèi)置類型,malloc/free和new/delete確實沒有什么區(qū)別,二者的作用完全一樣。
例:
int main()
{
//malloc向內(nèi)存申請4個整型大小的空間
int* p1 = (int*)malloc(sizeof(int) * 4);
//new向內(nèi)存申請4個整型大小的空間
int* p2 = new int[4];
//free釋放掉p1申請的空間
free(p1);
p1 = nullptr;
//delete釋放掉p2申請的空間
delete[] p2;
return 0;
}

3.2 new/delete操作自定義類型
class Date
{
public:
Date(int year=2021, int month=1, int day=1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
//malloc申請十個Date空間
Date* p1 = (Date*)malloc(sizeof(Date) * 10);
free(p1);
//new申請十個Date空間
Date* p2 = new Date[10];
delete[] p2;
return 0;
}
區(qū)別:在申請自定義類型空間的時候,new會調(diào)用構(gòu)造函數(shù),delete會調(diào)用析構(gòu)函數(shù),而mallo和free不會哦!
4.new和delete底層實現(xiàn)原理(important!!!)

在講解他們的底層實現(xiàn)原理之前需要先先介紹一下兩個全局函數(shù),分別是operator new 和operator delete.
new和delete是用戶進行動態(tài)內(nèi)存申請和釋放的操作符,operator new和operator delete是系統(tǒng)提供的全局函數(shù),new在底層調(diào)用operator new全局函數(shù)來申請空間,delete在底層通過調(diào)用operator delete全局函數(shù)來釋放空間。
4.1operator new/operator delete
operator new封裝了 malloc 和失敗拋異常倆個部分,
下面是operator new的代碼:
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
// try to allocate size bytes
void* p;
while ((p = malloc(size)) == 0) //如果開辟成功就不會進入循環(huán),并且可以清晰
if (_callnewh(size) == 0)
{
// report no memory
// 如果申請內(nèi)存失敗了,這里會拋出bad_alloc 類型異常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
類似的,operator delete封裝了free
下面是operator delete的代碼:
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;
}
總結(jié):通過觀察上述倆個全局函數(shù)的實現(xiàn),不難發(fā)現(xiàn)operator new實際也是通過malloc來申請空間,如果malloc申請空間成功就直接返回,否則執(zhí)行用戶提供的空間不足應(yīng)對措施,如果用戶提供該措施就繼續(xù)申請,否則就拋異常,operator delete最終是通過free來釋放空間的。
4.2new和delete的實現(xiàn)原理
內(nèi)置類型
malloc/free與new/delete在處理內(nèi)置類型時并沒有區(qū)別,只是malloc申請空間失敗時返回空指針,而new申請空間時是拋異常,new/delete申請和釋放的是單個元素的空間,new[]和delete[]申請的是連續(xù)空間。
自定義類型
1.new的原理:1.調(diào)用operator new函數(shù)申請空間。2.在申請空間上執(zhí)行構(gòu)造函數(shù),完成對象的初始化。
2.delete的原理:1.在空間上執(zhí)行析構(gòu)函數(shù),完成對象中資源的清理工作。2.調(diào)用operator delete函數(shù)釋放空間。
另外new T[N]的原理:調(diào)用operator new[]函數(shù),在operator new[]中實際調(diào)用N次operator new函數(shù)完成N個對象空間的申請,然后在申請的空間上執(zhí)行N次構(gòu)造函數(shù)。**delete[]的原理:**在釋放的對象空間上執(zhí)行N次析構(gòu)函數(shù),完成N個對象中資源的清理。然后調(diào)用operator delete[]釋放空間,實際在operator delete[]中調(diào)用N次operator delete來釋放空間。
初學(xué)者看到“delete調(diào)用析構(gòu)函數(shù),完成對象資源的清理工作,后邊又調(diào)用operator delete函數(shù)釋放空間”這部分內(nèi)容時可能會比較混亂,這里以棧為例子詳細說下:
struct Stack
{
int* _a;
int _top;
int _capacity;
Stack(int capacity = 4)
:_a(new int[capacity])
,_size(0)
,_capacity(capacity)
{
cout << "Stack(int capacity = 4)" << endl;
}
~Stack()
{
delete _a;
_top = _capacity = 0;
cout << "~Stack()" << endl;
}
};
int main()
{
Stack st;
Stack* ps = new Stack;
delete ps;
return 0;
}

首先,創(chuàng)建st變量,存放在棧當(dāng)中,然后調(diào)用構(gòu)造函數(shù)_a申請空間(對應(yīng)上圖動作1)。
接著,對于ps,會先去堆上調(diào)用operator new開辟一塊空間(對應(yīng)上圖動作2),再調(diào)用構(gòu)造函數(shù)對對象進行初始化,初始化時_a又會申請空間(對應(yīng)上圖動作3)
最后,delete[] ps,會先調(diào)用析構(gòu)函數(shù)完成對象資源的清理,即釋放_ a申請的空間,然后調(diào)用operator delete釋放ps申請的空間,然后調(diào)用析構(gòu)函數(shù) _ a申請的空間。(就是步驟321)
5.相關(guān)面經(jīng)
5.1malloc/free與new/delete的區(qū)別
1.malloc/free是函數(shù),而new/delete是操作符。
2.malloc申請的空間不會初始化,而new申請的空間可以初始化(內(nèi)置類型new也不會初始化)。
3.malloc申請空間時需要手動計算要申請的空間的字節(jié)數(shù),而new申請空間只需要所申請的類型即可。
4.malloc的返回值為void*,使用是需要強制類型轉(zhuǎn)換,而new不需要,因為new跟的是空間的類型。
5.對于申請內(nèi)存失敗,malloc的處理是返回空指針NULL,而new的處理是拋異常
6.對于自定義類型,new/delete會調(diào)用其構(gòu)造/析構(gòu)函數(shù),而malloc/delete不會。
5.2什么是內(nèi)存泄漏?
內(nèi)存泄漏指因為疏忽或錯誤造成程序未能釋放已經(jīng)不再使用的內(nèi)存的情況。內(nèi)存泄漏并不是指內(nèi)存在物理上的消失,而是應(yīng)用程序分配某段內(nèi)存后,因為設(shè)計錯誤,失去了對該段內(nèi)存的控制,因而造成了內(nèi)存的浪費。

5.3內(nèi)存泄漏的危害
如果是長期運行的程序出現(xiàn)內(nèi)存泄漏,影響很大,如操作系統(tǒng)、后臺服務(wù)等等,出現(xiàn)內(nèi)存泄漏會導(dǎo)致響應(yīng)越來越慢,最終卡死。
比如王者榮耀后臺服務(wù),長期運行,只有升級的時候才會停,內(nèi)存泄漏會導(dǎo)致可用內(nèi)存越來越少,程序越來越慢,甚至掛掉。
再比如物聯(lián)網(wǎng)設(shè)備:各種智能家居、智能機器人等等,它們內(nèi)存很小,也經(jīng)不起內(nèi)存泄漏的折騰。
by the way,對于C++我們需要主動釋放內(nèi)存,但是在Java當(dāng)中,不再需要主動釋放內(nèi)存,Java后臺有垃圾回收器,接管了內(nèi)存釋放(所以Java寫得好舒服,嗚嗚嗚)
5.4如何預(yù)防內(nèi)存泄漏(先了解一下,后續(xù)作者再詳細介紹)
1.智能指針
2.內(nèi)存泄漏檢測工具
2.1在linux環(huán)境下:

2.2在Windows環(huán)境下使用第三方工具:VLD工具

原理:以Visual Leak Detector為例,其工作分為3步,首先在初始化注冊一個鉤子函數(shù);然后在內(nèi)存分配時該鉤子函數(shù)被調(diào)用以記錄下當(dāng)時的現(xiàn)場;最后檢查堆內(nèi)存分配鏈表以確定是否存在內(nèi)存泄漏并將泄漏內(nèi)存的現(xiàn)場轉(zhuǎn)換成可讀的形式輸出。
感謝您的閱讀?。?!如果內(nèi)容對你有幫助的話,記得給我三連(點贊、收藏、關(guān)注)——做個手有余香的人。
到此這篇關(guān)于C++ 內(nèi)存管理原理分析的文章就介紹到這了,更多相關(guān)C++ 內(nèi)存管理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語言實現(xiàn)訪問及查詢MySQL數(shù)據(jù)庫的方法
這篇文章主要介紹了C語言實現(xiàn)訪問及查詢MySQL數(shù)據(jù)庫的方法,涉及C語言基于libmysql.lib實現(xiàn)訪問MySQL數(shù)據(jù)庫的相關(guān)操作技巧,需要的朋友可以參考下2018-01-01
C語言數(shù)據(jù)結(jié)構(gòu)實現(xiàn)字符串分割的實例
這篇文章主要介紹了C語言數(shù)據(jù)結(jié)構(gòu)實現(xiàn)字符串分割的實例的相關(guān)資料,希望通過本文能幫助到大家實現(xiàn)這樣的功能,需要的朋友可以參考下2017-10-10
C++實現(xiàn)中綴表達式轉(zhuǎn)化為后綴表達式詳解
這篇文章主要為大家詳細介紹了如何利用C++解決實現(xiàn)中綴表達式轉(zhuǎn)換為后綴表達式的問題,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03

