C/C++深入講解內(nèi)存管理
C/C++內(nèi)存分布
??首先我們來看一看以下代碼中變量在內(nèi)存中的存儲位置。

c/c++內(nèi)存分配圖:

1.棧又叫做堆棧,存儲非靜態(tài)局部變量/函數(shù)參數(shù)/返回值等等,棧是向下增長的。
2.內(nèi)存映射段是高效的I/O映射方式,用于裝載一個共享的動態(tài)內(nèi)存庫。用戶可使用系統(tǒng)接口創(chuàng)建共享內(nèi)存,做進(jìn)程間通信。
3.堆用于程序運行時動態(tài)內(nèi)存分配,堆上可以向上增長的。
4.數(shù)據(jù)段 - 用于存儲全局?jǐn)?shù)據(jù)和靜態(tài)數(shù)據(jù)。
5.代碼段 - 可執(zhí)行的代碼/只讀常量。
C語言中的動態(tài)內(nèi)存管理
malloc/calloc/realloc/free

int main()
{
int* p1 = (int*)malloc(sizeof(int));
int* p2 = (int*)calloc(4, sizeof(int));
int* p3 = (int*)realloc(p2, sizeof(int) * 10);
free(p1);
free(p2);
free(p3);
return 0;
}
malloc/calloc/realloc的區(qū)別?
malloc - 堆上動態(tài)開辟空間
realloc - 堆上動態(tài)開辟空間 + 初始化為0 (相當(dāng)于malloc + memset)
calloc - 針對已經(jīng)有的空間進(jìn)行擴(kuò)容 (原地擴(kuò)容或異地擴(kuò)容)
C++的內(nèi)存管理
??C語言的內(nèi)存管理方式在c++中可以繼續(xù)使用,但是有些地方使用起來就比較麻煩了。因此c++提供了自己的內(nèi)存管理方式:通過new和delete操作符進(jìn)行動態(tài)內(nèi)存管理。 new和delete是運算符,不是函數(shù),因此執(zhí)行效率高。
new和delete操作內(nèi)置類型::
int main()
{
// new/delete和malloc/free 針對內(nèi)置類型沒有任何差別,只是用法不同
//動態(tài)申請一個int類型的空間
int* p1 = new int;
delete p1;
//動態(tài)申請一個int類型的空間并初始化為10
int* p2 = new int(10);
delete p2;
//動態(tài)申請10個int類型的空間
int* p3 = new int[10];
delete[] p3;
return 0;
}
注意:申請和釋放單個元素的空間,使用new和delete操作符,申請和釋放連續(xù)的空間,使用new[]和delete[]。
new和delete操作自定義類型::

注意: 在申請自定義類型的空間時,new會調(diào)用構(gòu)造函數(shù),delete會調(diào)用析構(gòu)函數(shù),而malloc和free不會。
??對于以上總結(jié)一下:
1.c++中如果是申請內(nèi)置類型對象或者數(shù)組,malloc和new沒有什么區(qū)別。
2.如果是自定義類型,那么區(qū)別很大,new和delete是開空間 + 初始化,析構(gòu)清理 + 釋放空間,malloc和free僅僅是開空間 + 釋放空間。
3.建議在c++中,無論是自定義類型還是內(nèi)置類型的申請和釋放,盡量使用new和delete。
operator new與operator delete函數(shù)
new和delete是用戶進(jìn)行動態(tài)內(nèi)存申請和釋放的操作符,operator new和 operator delete是系統(tǒng)提供的全局函數(shù),new在底層調(diào)用operator new 全局函數(shù)來申請空間,delete在底層提供operator delete全局函數(shù)來釋放空間。
如下是c++官方對于這兩個函數(shù)的描述:

operator new和operator delete的實現(xiàn)代碼:
/*
operator new:該函數(shù)實際通過malloc來申請空間,當(dāng)malloc申請空間成功時直接返回;申請空間失敗,
嘗試執(zhí)行空間不足應(yīng)對措施,如果改應(yīng)對措施用戶設(shè)置了,則繼續(xù)申請,否則拋異常。
*/
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
// 如果申請內(nèi)存失敗了,這里會拋出bad_alloc 類型異常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
/*
operator delete: 該函數(shù)最終是通過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;
}
/*
free的實現(xiàn)
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)
通過上訴兩個全局函數(shù)的實現(xiàn)代碼可以看出,operator new實際上是通過malloc來申請空間的,如果malloc申請空間成功就直接返回,否則執(zhí)行用戶提供的空間不足的應(yīng)對,如果用戶提供該措施就繼續(xù)申請,否則就拋異常。operator delete最終是通過free來釋放空間的。
operator new的使用案例:
struct ListNode
{
ListNode(int data = 0)
:_next(nullptr)
,_prev(nullptr)
,_data(data)
{}
ListNode* _next;
ListNode* _prev;
int _data;
};
//operator new的用法跟malloc和free是一樣的,都是在堆上申請空間
//只是申請空間失敗后的處理方式不一樣,malloc失敗返回NULL,operator new失敗以后拋異常
int main()
{
//C語言
ListNode* p1 = (ListNode*)malloc(sizeof(ListNode));
free(p1);
//c++
ListNode* p2 = (ListNode*)operator new(sizeof(ListNode));
operator delete(p2);
int* p3 = (int*)malloc(100000000000000000);
if (p3 == NULL)
{
cout << "malloc fail" << endl;
}
try
{
int* p4 = (int*)operator new(100000000000000000);
}
//開辟空間失敗,捕獲異常信息
catch (exception& e)
{
cout << e.what() << endl;
}
return 0;
}
operator delete的使用案例:
class A
{
public:
A(int a = 0)
{
cout << "A()" << this << endl;
}
~A()
{
cout << "~A()" << this << endl;
}
private:
int _a;
};
int main()
{
//c語言 -> A* p = (A*)malloc(sizeof(A));
//等價于直接用 A* p = new A;
A* p = (A*)operator new(sizeof(A));
new(p)A; // new(p)A(2); 定位new,placement-new,顯示調(diào)用構(gòu)造函數(shù)初始化這塊空間對象
//等價于 delete p
p->~A(); //析構(gòu)函數(shù)可以顯示調(diào)用
operator delete(p);
return 0;
}
operator new與operator delete的類專屬重載
內(nèi)存池:內(nèi)存池的主要作用是提高效率。通過一次性申請比較大的空間,來避免小空間內(nèi)存的頻繁申請和釋放,每次需要為對象分配內(nèi)存空間時,在已經(jīng)申請好的大的空間內(nèi)分配??臻e區(qū)被按照對象大小劃分為若干塊,每個塊之間通過鏈表連接起來。
??以下代碼演示了,針對鏈表的節(jié)點ListNode通過重載類專屬 operator new / operator delete ,實現(xiàn)鏈表節(jié)點使用內(nèi)存池申請和釋放內(nèi)存,提高效率。
struct ListNode
{
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* _next;
ListNode* _prev;
int _data;
};
class List
{
public:
List()
{
_head = new ListNode();
_head->_next = _head;
_head->_prev = _head;
}
~List()
{
ListNode* cur = _head->_next;
while (cur != _head)
{
ListNode* next = cur->_next;
delete cur;
cur = next;
}
delete _head;
_head = nullptr;
}
private:
ListNode* _head;
};
int main()
{
List h;
return 0;
}
new和delete的實現(xiàn)原理
內(nèi)置類型:
若申請的是內(nèi)置類型的空間,new和malloc,delete和free基本相似。new/delete 申請和釋放的是單個元素的空間,new[]和delete[]申請的是連續(xù)空間。new在空間申請失敗時會拋異常,malloc申請空間失敗則返回NULL。
自定義類型:
new:
- 調(diào)用operator new函數(shù)申請空間。
- 在申請的空間調(diào)用構(gòu)造函數(shù),完成對象的構(gòu)造。
delete:
- 在空間上調(diào)用析構(gòu)函數(shù),完成對象中資源清理的工作。
- 調(diào)用operator delete函數(shù)釋放對象的空間。
new arr[N]:
- 調(diào)用operator new[]函數(shù),在operator new[]中實際調(diào)用operator new函數(shù)完成N個對象空間的申請。
- 在申請的空間上執(zhí)行N次構(gòu)造函數(shù)。
delete []:
- 在釋放的空間上執(zhí)行N次析構(gòu)函數(shù),完成N個對象中資源的清理。
- 調(diào)用operator delete[]釋放空間,在operator delete[]中調(diào)用operator delete來釋放空間。
定位new表達(dá)式(placement-new)
??一般來說,使用new申請空間時,是從系統(tǒng)堆上分配空間。申請所得的空間位置是根據(jù)當(dāng)時的內(nèi)存使用的實際情況來決定。但在某些特殊情況下,可能需要在已分配的特定內(nèi)存創(chuàng)建對象,這就是所說的定位new(placement - new)。
??默認(rèn)情況下,如果new不能分配所需要的內(nèi)存空間,那么它會拋出一個類型為bad_alloc的異常。我們可以改變使用new的方式來阻止其拋出異常:
//如果申請失敗,new會返回一個空指針(NULL) int* p1 = new int; //如果分配空間失敗,new會拋出std::bad_alloc int* p2 = new (nothrow) int; //如果分配空間失敗,new返回一個空指針
這種形式的new我們就稱為定位new。
定位new表達(dá)式允許我們向new傳遞額外的參數(shù)。如上的nothrow。將nothrow傳給new,即不能拋出異常。如果這種形式的new不能分配所需內(nèi)存,那么它會返回一個空指針。


malloc/free和new/delete的區(qū)別
1.相同點:它們都是堆上申請空間并且手動釋放。
2.malloc和free是函數(shù),new和delete是操作符。
3.malloc申請的空間不初始化,new申請的空間可以初始化。
4.malloc申請空間時需要計算所需空間的大小并且傳遞,new申請空間時只需在其后加上空間類型即可。
5.malloc的返回值為void*,在使用時必須強(qiáng)轉(zhuǎn),new不需要,因為new后跟的是空間的類型。
6.malloc申請空間失敗時返回NULL,使用時需要判空,而new不需要判空,但是new需要捕獲異常。
7.申請自定義類型對象時,malloc/free只會開辟空間,不會調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)處理空間,但new在申請空間后會調(diào)用構(gòu)造函數(shù)完成對象的初始化,delete在釋放空間前后調(diào)用析構(gòu)函數(shù)完成空間中資源的清理。
內(nèi)存泄漏及其危害
內(nèi)存泄漏(Memory Leak) 是指程序中已動態(tài)分配的堆內(nèi)存由于某種原因?qū)е鲁绦蛭瘁尫呕蛘邿o法釋放,造成系統(tǒng)內(nèi)存的浪費,導(dǎo)致程序運行速度減慢甚至系統(tǒng)崩潰等嚴(yán)重后果。內(nèi)存泄漏并不是指內(nèi)存在物理上的消失,而是應(yīng)用程序分配某段內(nèi)存后,因為錯誤設(shè)計,失去了對該段內(nèi)存的控制。
??內(nèi)存泄漏的危害:長期運行的程序出現(xiàn)內(nèi)存泄漏,影響很大,如操作系統(tǒng)、后臺服務(wù)等等,出現(xiàn)內(nèi)存泄漏會導(dǎo)致響應(yīng)越來越慢,最終卡死。
??常見的導(dǎo)致內(nèi)存泄漏的原因:

??c/c++程序中我們比較關(guān)心的兩種內(nèi)存泄漏:
1.堆內(nèi)存泄漏(Heap Leak)
堆內(nèi)存指程序執(zhí)行中需要通過malloc、realloc、realloc、new等從堆中分配內(nèi)存,用完后需通過調(diào)用free或者delete釋放。若程序的設(shè)計錯誤導(dǎo)致這一部分內(nèi)存沒有被釋放掉,那么之后這塊空間將無法繼續(xù)使用,就會發(fā)生堆內(nèi)存泄漏。
2.系統(tǒng)資源泄漏
指程序使用系統(tǒng)分配的資源,比方套接字、文件描述符、管道等沒有使用對于的函數(shù)釋放掉,導(dǎo)致系統(tǒng)資源的浪費,嚴(yán)重則可導(dǎo)致系統(tǒng)的效能減少,系統(tǒng)執(zhí)行不穩(wěn)等。
??如何避免內(nèi)存泄漏:
1.工程前期設(shè)計規(guī)范,養(yǎng)成良好的編碼習(xí)慣。
2.提前預(yù)防。如智能指針等。
3.內(nèi)存泄漏工具的使用。(很多工具不靠譜,且收費昂貴)
??內(nèi)存泄漏檢測工具:(來自百度百科)

到此這篇關(guān)于C/C++深入講解內(nèi)存管理的文章就介紹到這了,更多相關(guān)C++內(nèi)存管理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++實現(xiàn)LeetCode(114.將二叉樹展開成鏈表)
這篇文章主要介紹了C++實現(xiàn)LeetCode(114.將二叉樹展開成鏈表),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07
C++基礎(chǔ)入門教程(七):一些比較特別的基礎(chǔ)語法總結(jié)
這篇文章主要介紹了C++基礎(chǔ)入門教程(七):一些比較特別的基礎(chǔ)語法總結(jié),本文總結(jié)的都是一些特殊的語法,需要的朋友可以參考下2014-11-11
深入探索C++中stack和queue的底層實現(xiàn)
這篇文章主要介紹了C++中的stack和dequeue的底層實現(xiàn),本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-09-09

