C/C++內(nèi)存管理之new與delete的使用及原理解析
一、C/C++中程序內(nèi)存區(qū)域劃分

內(nèi)存區(qū)域相關(guān)作用:
- 棧又叫堆棧:非靜態(tài)局部變量、函數(shù)參數(shù)、返回值等等,棧是向下增長(zhǎng)的
- 內(nèi)存映射段時(shí)高效的I/O映射方式,用于裝載一個(gè)共享的動(dòng)態(tài)內(nèi)存庫(kù),用戶可以使用系統(tǒng)接口創(chuàng)建共享共享內(nèi)存,做進(jìn)程間通信
- 堆用于程序運(yùn)行時(shí)動(dòng)態(tài)內(nèi)存分配,堆時(shí)可以上增長(zhǎng)的
- 數(shù)據(jù)段:存儲(chǔ)全局?jǐn)?shù)據(jù)和靜態(tài)數(shù)據(jù)
- 代碼段:可執(zhí)行的代碼、只讀常量
在語(yǔ)法上將數(shù)據(jù)段稱為靜態(tài)區(qū)、代碼段稱為常量區(qū),而以上操作系統(tǒng)的命名。
提出相關(guān)思考:
- 為什么要分不同的區(qū)域
- 哪個(gè)區(qū)域是我們需要重點(diǎn)關(guān)注的
回答:
- 根據(jù)對(duì)象不同的生命周期和作用域,分配到不同的區(qū)域中,統(tǒng)一管理,高效地對(duì)對(duì)象進(jìn)行處理
- 堆是我們要需要重點(diǎn)關(guān)注的,這是系統(tǒng)留給我們控制的內(nèi)存,其他系統(tǒng)是自動(dòng)的
1.1 相關(guān)練習(xí)測(cè)試
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";
const char* pChar3 = "abcd";
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(ptr3);
}
答案:
- 選擇題:C、C、C、A、A 。A、A、A、D、A、B
- 填空題:40、5、4、4/8、4、4/8
這里容易混洗的char str1[] ="abcd"與const char* str2 ="abcd"。這里str1是個(gè)數(shù)組將常量拷貝到數(shù)組,而str2是直接指向常量區(qū)中常量。

二、C語(yǔ)言中動(dòng)態(tài)內(nèi)存管理方式
C語(yǔ)言中,系統(tǒng)通過(guò)一系列函數(shù)賦予了我們對(duì)堆上空間的控制
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 );
}提出思考:
- malloc/calloc/realloc的區(qū)別是什么?
- 這里使用realloc是否還需要free(p2)
- malloc的實(shí)現(xiàn)原理?
第一個(gè)問(wèn)題的回答:
對(duì)于malloc/calloc/realloc是系統(tǒng)為我們提供在堆上申請(qǐng)空間的途徑。在功能上大體是相同的,對(duì)于malloc與calloc這兩個(gè)函數(shù),除了參數(shù)部分及其是否完成初始化,其他功能是相同的。
relloc比較特別,屬于擴(kuò)容時(shí)使用的函數(shù)。擴(kuò)容有兩種方式:原地?cái)U(kuò)容和異地?cái)U(kuò)容。如果realloc第一個(gè)參數(shù)部分為空,可以當(dāng)作malloc使用)。具體還是參考下這篇博客有詳細(xì)解釋內(nèi)存管理
第二個(gè)問(wèn)題的回答:
由于realloc進(jìn)行了擴(kuò)容操作。如果是原地?cái)U(kuò)容,在原來(lái)開辟空間上完成擴(kuò)容操作,這里p3會(huì)同p2指向這塊空間,只需要free(p3);如果是異地?cái)U(kuò)容,將p2空間中數(shù)據(jù)拷貝一份,在堆上找一塊空間充足地方,完成擴(kuò)容和拷貝操作,p2指向原空間,會(huì)被系統(tǒng)自動(dòng)收回,不需要對(duì)p2進(jìn)行free操作。對(duì)此無(wú)論是原地還是異地,只需要free(p3)即可
第三個(gè)問(wèn)題的回答:
可以通過(guò)該鏈接進(jìn)行學(xué)習(xí)GLibc堆利用入門
三、C++內(nèi)存管理方式
在C++中,雖然可以繼續(xù)使用C語(yǔ)言對(duì)于內(nèi)存管理方式,但是在有些地方就無(wú)能為力,而且使用起來(lái)比較麻煩。對(duì)此因此C++又提出了自己的內(nèi)存管理方式:通過(guò)new和delete操作符進(jìn)行動(dòng)態(tài)內(nèi)存管理
3.1 使用new/delete進(jìn)行數(shù)據(jù)操作
3.1.1 new/delete 操作內(nèi)置類型
int main()
{
//動(dòng)態(tài)申請(qǐng)一個(gè)int類型的空間
int* ptr1 = new int;
//動(dòng)態(tài)申請(qǐng)一個(gè)int類型的空間并且初始化為10
int* ptr2 = new int(10);
//動(dòng)態(tài)申請(qǐng)10個(gè)int類型的空間
int* ptr3 = new int[3];
//動(dòng)態(tài)申請(qǐng)10個(gè)int類型的空間并且完成初始化
int* ptr4 = new int[10]{ 1,2,3 };//剩下沒有明確給值,默認(rèn)為0
delete ptr1;
delete ptr2;
delete []ptr3;
delete[]ptr4;
return 0;
}
注意需要匹配使用new和delete操作符:
- 申請(qǐng)和釋放單個(gè)元素的空間:new、delete
- 申請(qǐng)和釋放多個(gè)元素的空間:new[]、delete[]
3.1.2 new和delete操作自定義類型
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
//自定義類型
A* p1 = (A*)malloc(sizeof(A));
A* p2 = new A(1);
free(p1);
delete p2;
//內(nèi)置類型
int* p3 = (int*)malloc(sizeof(int));
int* p4 = new int;
free(p3);
delete p4;
//開辟連續(xù)自定義類型空間
A* p5 = (A*)malloc(sizeof(A) * 10);
A* p6 = new A[10];
free(p5);
delete[] p6;
return 0;
}
從結(jié)果上來(lái)看,對(duì)于new與malloc最大差別在于對(duì)自定義類型除了開辟空間以外,還會(huì)調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)及其進(jìn)行良好的初始化和控制。對(duì)于malloc而言無(wú)法對(duì)自定義類型進(jìn)行好的初始化和控制,只負(fù)責(zé)開辟內(nèi)存,除此之外內(nèi)置類型幾乎相同(初始化不同)
對(duì)于new優(yōu)于malloc的幾點(diǎn):
- 用法上進(jìn)行調(diào)正,更簡(jiǎn)潔好用
- 可以控制初始化
- 對(duì)于自定義類型,new可以開空間+構(gòu)造函數(shù)
- new配合構(gòu)造函數(shù),可以更加便捷創(chuàng)建節(jié)點(diǎn)等
- new失敗了以后拋異常,不需要手動(dòng)檢查
第一點(diǎn):
int* p0 = (int*)malloc(sizeof(int)); int* p1 = new int;
第二點(diǎn):
int* p2 = new int[10];
int* p3 = new int(10);
int* p4 = new int[10]{ 1,2,3 };第三點(diǎn):
struct ListNode
{
ListNode* _next;
int _val;
ListNode(int val)
:_val(val)
,_next(nullptr)
{}
};
//創(chuàng)建不帶哨兵位,同時(shí)如果是插入數(shù)據(jù),new ListNode(3)即可
ListNode* CreateList(int n)
{
ListNode head(-1);
ListNode* tail = &head;
int val;
printf("請(qǐng)依次輸入%d個(gè)節(jié)點(diǎn)的值:>", n);
for (size_t i = 0; i < n; i++)
{
cin >> val;
tail->_next = new ListNode(val);
tail = tail->_next;
}
return head._next;
}第四點(diǎn):
void func()
{
int n = 1;
while (1)
{
int* p = new int[1024 * 1024*100];
cout <<n<<"->"<< p << endl;
++n;
}
}
int main()
{
try
{
func();
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
這里try和catch就是捕捉異常,這一點(diǎn)到后面有涉及。以上種種都是new的優(yōu)點(diǎn),所以我們不推薦再使用malloc/free系列。
四、 new和delete原理及其兩個(gè)全局函數(shù)的實(shí)現(xiàn)(operator new/operator delerte)
new和delete是用戶進(jìn)行動(dòng)態(tài)內(nèi)存申請(qǐng)和釋放的操作符,operator new和operator delete是系統(tǒng)提供的全局函數(shù),new再底層調(diào)用operator new全局函數(shù)來(lái)申請(qǐng)空間,delete在底層通過(guò)operator delete全局函數(shù)來(lái)釋放空間(operator new與operator delete不是對(duì)new和delete的重載)
int* p1 = (int*)operator new(10 * 4); //int* p1=new int(10*4) operator delete(p1); //delete(p1)
從代碼中可以看出來(lái),new/delete和operator new/operator delete效果上是相同的。那么直接使用new/delete就行,operator new/operator delete對(duì)于我們來(lái)說(shuō)是沒用的,但是有這個(gè)東西說(shuō)明在系統(tǒng)中有它們的一席之地的。
/*
operator new:該函數(shù)實(shí)際通過(guò)malloc來(lái)申請(qǐng)空間,當(dāng)malloc申請(qǐng)空間成功時(shí)直接返回;申請(qǐng)空間
失敗,嘗試執(zhí)行空 間不足應(yīng)對(duì)措施,如果改應(yīng)對(duì)措施用戶設(shè)置了,則繼續(xù)申請(qǐng),否則拋異常。
*/
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
// try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
//通過(guò)上述兩個(gè)全局函數(shù)的實(shí)現(xiàn)知道,operator new 實(shí)際也是通過(guò)malloc來(lái)申請(qǐng)空間,如果
//malloc申請(qǐng)空間成功就直接返回,否則執(zhí)行用戶提供的空間不足應(yīng)對(duì)措施,如果用戶提供該措施
//就繼續(xù)申請(qǐng),否則就拋異常。operator delete 最終是通過(guò)free來(lái)釋放空間的。
if (_callnewh(size) == 0)
{
// report no memory
// 如果申請(qǐng)內(nèi)存失敗了,這里會(huì)拋出bad_alloc 類型異常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
/*
operator delete: 該函數(shù)最終是通過(guò)free來(lái)釋放空間的
*/
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;
}
/*
free的實(shí)現(xiàn)
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)可以通過(guò)上述兩個(gè)全局函數(shù)的實(shí)現(xiàn),可以知道,operator new實(shí)際是通過(guò)malloc來(lái)申請(qǐng)空間,如果malloc申請(qǐng)空間成功就直接返回,否則執(zhí)行用戶提供的空間不足應(yīng)對(duì)措施;如果用戶提供該措施就繼續(xù)申請(qǐng),否則就拋異常。operator delete最終是通過(guò)free來(lái)釋放空間的。
內(nèi)置類型:
如果申請(qǐng)的是內(nèi)置類型的空間,new/malloc與delete/free基本類似,不同的地方是new在申請(qǐng)空間失敗時(shí)會(huì)拋異常,malloc會(huì)返回NULL
自定義類型:
new的原理:
- 調(diào)用operator new函數(shù)申請(qǐng)空間
- 在申請(qǐng)的空間上執(zhí)行構(gòu)造函數(shù),完成對(duì)象的構(gòu)造
delete的原理:
- 在空間上執(zhí)行析構(gòu)函數(shù),完成對(duì)象中資源的清理工作
- 調(diào)用operator delete含函數(shù)釋放對(duì)象的空間
new T[N]的原理:
- 調(diào)用operator new[]函數(shù),在operator new[]中實(shí)際調(diào)用operator new函數(shù)完成N個(gè)對(duì)象空間的申請(qǐng)
- 在申請(qǐng)的空間上執(zhí)行N次構(gòu)造函數(shù)
delete[]的原理:
- 在釋放的對(duì)象空間上執(zhí)行N次析構(gòu)函數(shù),完成N個(gè)對(duì)象中資源的清理
- 調(diào)用operator delete[]釋放空間,實(shí)際在operator delete[]中調(diào)用operator delete來(lái)釋放空間
通過(guò)匯編,深入立即其中

對(duì)于自定義類型轉(zhuǎn)換指令只有兩個(gè)核心動(dòng)作調(diào)用全局函數(shù)及其構(gòu)造或析構(gòu),而內(nèi)置類型只有調(diào)用全局函數(shù)。
對(duì)此可得:
- operator new是對(duì)malloc的封裝,如果失敗拋異常,實(shí)現(xiàn)new
- operator newp[]封裝operator new,最終還是malloc
- operator delete對(duì)free的封裝
- operator delete[]封裝operator delete


同時(shí)這里需要注意調(diào)用順序上的問(wèn)題
五、深入了解new和delete工作原理
new是個(gè)操作符,在編譯時(shí)new A會(huì)轉(zhuǎn)化為匯編指令調(diào)用malloc,一般來(lái)說(shuō)malloc失敗會(huì)返回空,由于C++是面向?qū)ο蟮倪^(guò)程,malloc失敗返回空是不太合適,一般采用拋異常。全局函數(shù)operator new來(lái)封裝malloc,去調(diào)正失敗的返回情況。

int main()
{
A* p1 = new A;//operator new+1次構(gòu)造
A* p2 = new A[10];//operatorn new[]+10構(gòu)造
int* p3=new int[10];//operator new[](占用40個(gè)字節(jié))
delete p1;//1次析構(gòu)+operator delete
delete[] p2;//?次析構(gòu)+operator delete
delete[] p3;//operator delete
return 0;
}結(jié)合匯編和代碼提供的信息,提出以下問(wèn)題:
- 編譯器如何開始確定所需開辟空間大小
- 為什么p2指向大小為44字節(jié)空間,而不是40字節(jié)空間
- 為什么編譯器知道p2需要調(diào)用10次析構(gòu)函數(shù)
回答:
- 由于new屬于操作符,在編譯時(shí)就計(jì)算出了所需空間的大小。
- 編譯器在所開辟空間位置前面,也是調(diào)用operator new函數(shù)多開四個(gè)字節(jié),用于記錄對(duì)象個(gè)數(shù)(針對(duì)自定義類型)
- 由于內(nèi)置類型不需要調(diào)用析構(gòu)函數(shù),對(duì)此不需要記錄對(duì)象個(gè)數(shù),而自定義類型需要記錄對(duì)象個(gè)數(shù)。delete[]需要通過(guò)對(duì)象個(gè)數(shù)才知道調(diào)用多少次析構(gòu)函數(shù)。如果將析構(gòu)函數(shù)注釋,p2占用空間為40字節(jié)。由于編譯器會(huì)自動(dòng)生成析構(gòu)函數(shù),而該析構(gòu)函數(shù)沒有發(fā)揮占用,編譯器會(huì)優(yōu)化導(dǎo)致不需要四個(gè)字節(jié)記錄對(duì)象個(gè)數(shù),具體需要看編譯器是否優(yōu)化
六、malloc/free系列和new/delete系列的區(qū)別
我們將通過(guò)用法和底層特性兩點(diǎn)說(shuō)明:
共同點(diǎn):
- 都是從堆上申請(qǐng)空間,并且需要用戶手動(dòng)釋放
不同點(diǎn):
- malloc和free屬于函數(shù),new和delete屬于操作符
- malloc申請(qǐng)的空間不會(huì)初始化,new可以初始化
- malloc申請(qǐng)空間時(shí),需要手動(dòng)計(jì)算空間并且傳遞,new只需在其后跟上空間的類型即可,如果是多個(gè)對(duì)象,[]中指定對(duì)象個(gè)數(shù)即可
- malloc的返回值為void*,在使用事必須強(qiáng)轉(zhuǎn),new不需要,因?yàn)閚ew后跟的是空間的類型
- malloc申請(qǐng)空間失敗時(shí),返回的是NULL,因此使用時(shí)必須判空,new不需要的,但是new需要捕獲異常
- 申請(qǐng)自定義類型對(duì)象時(shí),malloc/free只會(huì)開辟空間,不會(huì)調(diào)用構(gòu)造函數(shù)與析構(gòu)函數(shù),而new在申請(qǐng)空間后會(huì)調(diào)用構(gòu)造函數(shù)完成對(duì)象的初始化,delete在釋放空間前會(huì)調(diào)用析構(gòu)函數(shù)完成空間中資源的清理
七、delete最好匹配使用

解析說(shuō)明:圖中delete沒有匹配使用,導(dǎo)致可能報(bào)錯(cuò)。這里p2指向并不是申請(qǐng)空間的第一個(gè)位置,第一個(gè)位置是operator new[]實(shí)現(xiàn)存放對(duì)象個(gè)數(shù)申請(qǐng)的空間。由于空間是不能一塊塊釋放,對(duì)此p2釋放的位置是錯(cuò)誤的,并且不明確需要調(diào)用多少次析構(gòu)函數(shù),可能會(huì)造成內(nèi)存泄漏。如果是delete[] p2,會(huì)將p2指針偏移前面四個(gè)字節(jié)。
但是以上種種情況,導(dǎo)致這個(gè)問(wèn)題是否報(bào)錯(cuò),具體需要看編譯器是否進(jìn)行優(yōu)化(編譯器是否調(diào)用析構(gòu)函數(shù)),對(duì)此我們只需要正確的使用delete就行,上面只是了解就行了
八、定位new表達(dá)式(placement -new)(了解)
定位new表達(dá)式時(shí)在已分配的原始內(nèi)存空間中調(diào)用構(gòu)造函數(shù)初始化一個(gè)對(duì)象。
new(指針->空間)類型() :顯式調(diào)用構(gòu)造函數(shù)對(duì)已經(jīng)有的空間初始化
構(gòu)造函數(shù)不能顯式調(diào)用,析構(gòu)可以顯式調(diào)用(一般不會(huì)去調(diào)用兩次析構(gòu)的)
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
}
/ 定位new/replacement new
int main()
{
// p1現(xiàn)在指向的只不過(guò)是與A對(duì)象相同大小的一段空間,還不能算是一個(gè)對(duì)象,因?yàn)闃?gòu)造函數(shù)沒
有執(zhí)行
A* p1 = (A*)malloc(sizeof(A));
new(p1)A; // 注意:如果A類的構(gòu)造函數(shù)有參數(shù)時(shí),此處需要傳參
p1->~A();
free(p1);
A* p2 = (A*)operator new(sizeof(A));
new(p2)A(10);
p2->~A();
operator delete(p2);
return 0;
}一般沒有人會(huì)使用,因?yàn)檫@里就是把new分成兩部分,那么干嘛不直接使用new更加方便。
使用場(chǎng)景:定位new表達(dá)式在實(shí)際中,一般是配合內(nèi)存池使用。因?yàn)閮?nèi)存池分配出的內(nèi)存沒有初始化。如果是自定義類型的對(duì)象,需要使用new的定義表達(dá)式進(jìn)行顯式調(diào)構(gòu)造函數(shù)進(jìn)行初始化。
C++基本放棄了malloc/free系列。關(guān)于realloc擴(kuò)容解決措施,在C++相關(guān)容器中它們會(huì)自動(dòng)處理內(nèi)存的擴(kuò)容,使得開發(fā)者可以更加方便地使用動(dòng)態(tài)大小的數(shù)據(jù)集合。
九、內(nèi)存泄漏(了解)
9.1 內(nèi)存泄漏概念
內(nèi)存泄漏指因?yàn)槭韬龌蛘咤e(cuò)誤造成程序未能釋放已經(jīng)不再使用的內(nèi)存的情況。內(nèi)存泄漏并不是指內(nèi)存在物理上的消失,而是應(yīng)用程序分配某段內(nèi)存后,因?yàn)樵O(shè)計(jì)錯(cuò)誤,失去了對(duì)該段內(nèi)存的控制,因而造成了內(nèi)存的浪費(fèi)
9.2 內(nèi)存泄漏的危害
長(zhǎng)期運(yùn)行的程序出現(xiàn)內(nèi)存泄漏,影響很大,如操作系統(tǒng)、后臺(tái)服務(wù)等等,出現(xiàn)內(nèi)存泄漏會(huì)導(dǎo)致響應(yīng)越來(lái)越慢,最終卡死。
9.3 內(nèi)存泄漏分類
C/C++程序中一般我們關(guān)心兩種方面的內(nèi)存泄漏
1.堆內(nèi)存泄漏(Heap leak)
堆內(nèi)存指的是程序執(zhí)行種依據(jù)須要分配通過(guò)malloc/calloc/realloc/new等從堆中分配的一塊內(nèi)存,用完后必須通過(guò)調(diào)用相應(yīng)的free或者delete刪除。假設(shè)程序的設(shè)計(jì)錯(cuò)誤導(dǎo)致這部分內(nèi)存沒有被釋放,那么以后這部分空間將無(wú)法再被使用,就會(huì)產(chǎn)生Heap Leak。
2.系統(tǒng)資源泄漏
指程序使用系統(tǒng)分配的資源,比方套接字,文件描述符,管道等沒有使用對(duì)應(yīng)的函數(shù)釋放掉,導(dǎo)致系統(tǒng)資源的浪費(fèi),嚴(yán)重可導(dǎo)致系統(tǒng)效能減少,系統(tǒng)執(zhí)行不穩(wěn)定。
到此這篇關(guān)于C/C++內(nèi)存管理:new與delete的使用及原理的文章就介紹到這了,更多相關(guān)C++內(nèi)存管理new與delete內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
簡(jiǎn)單比較C語(yǔ)言中的execl()函數(shù)與execlp()函數(shù)
這篇文章主要介紹了C語(yǔ)言中的execl()函數(shù)與execlp()函數(shù)的簡(jiǎn)單比較,是C語(yǔ)言入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-08-08
C語(yǔ)言實(shí)現(xiàn)密碼本小項(xiàng)目
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)密碼本小項(xiàng)目,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-02-02
visual studio code 配置C++開發(fā)環(huán)境的教程詳解 (windows 開發(fā)環(huán)境)
這篇文章主要介紹了 windows 開發(fā)環(huán)境下visual studio code 配置C++開發(fā)環(huán)境的圖文教程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03

