摘要:對于申請內存失敗,的處理是返回空指針,而的處理是拋異常對于自定義類型,會調用其構造析構函數,而不會。內存泄漏并不是指內存在物理上的消失,而是應用程序分配某段內存后,因為設計錯誤,失去了對該段內存的控制,因而造成了內存的浪費。
前言:本章主要介紹C++的內存管理,以C++的內存分布作為引入,介紹C++不同于C語言的內存管理方式(new delete對比 malloc free),最后為了加深讀者的理解,會介紹new和delete的底層實現原理。
C/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"; 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);}
上述代碼段對應變量區域劃分如下:
再來回顧一下之前C語言部分的動態內存管理方式:
malloc / calloc/ realloc和free
帶著兩個問題閱讀下述程序段:
1.malloc / calloc/ realloc
的區別是什么?
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相當于malloc+memset(0),即開空間+初始化。
2.realloc是對malloc/calloc的空間進行擴容,擴容之下又涉及到了咱們前面所講的原地擴容和異地擴容倆種情景:原地擴容p2和p3是一樣的,也有可能是異地擴容,那么p2指向的空間已經被釋放了,所以兩種情況下我們都可以不需要處理p2。
總之就是C語言那套內存管理方式相對麻煩,所以C++提出了自己的內存管理方式:通過new和delete操作符進行動態內存管理.
1.開辟單個元素
開辟單個元素基本語法:
type * ptr = new type(content);
,可以理解成動態申請一個type類型的空間并將其中內容初始化為content,當然,你也可以選擇不初始化。釋放空間語法:
delete name;
例:
int* a = new int(100); //動態申請一個int類型的空間并初始化為100delete a;
2.開辟n個type類型的空間
開辟n個type類型基本語法:
type* name = new type[n]
刪除的基本語法:
delete[] name;
例:
int* ptr = new int[100];//動態申請100個int類型的空間delete[] ptr; //注意哦!一定要匹配哦!不然必崩潰!
3.對于內置類型,malloc/free和new/delete確實沒有什么區別,二者的作用完全一樣。
例:
int main(){ //malloc向內存申請4個整型大小的空間 int* p1 = (int*)malloc(sizeof(int) * 4); //new向內存申請4個整型大小的空間 int* p2 = new int[4]; //free釋放掉p1申請的空間 free(p1); p1 = nullptr; //delete釋放掉p2申請的空間 delete[] p2; return 0;}
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;}
區別:在申請自定義類型空間的時候,new會調用構造函數,delete會調用析構函數,而mallo和free不會哦!
在講解他們的底層實現原理之前需要先先介紹一下兩個全局函數,分別是
operator new
和operator delete
.new和delete是用戶進行動態內存申請和釋放的操作符,operator new和operator delete是系統提供的全局函數,new在底層調用operator new全局函數來申請空間,delete在底層通過調用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) //如果開辟成功就不會進入循環,并且可以清晰 if (_callnewh(size) == 0) { // report no memory // 如果申請內存失敗了,這里會拋出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;}
總結:通過觀察上述倆個全局函數的實現,不難發現operator new實際也是通過malloc來申請空間,如果malloc申請空間成功就直接返回,否則執行用戶提供的空間不足應對措施,如果用戶提供該措施就繼續申請,否則就拋異常,operator delete最終是通過free來釋放空間的。
malloc/free與new/delete在處理內置類型時并沒有區別,只是malloc申請空間失敗時返回空指針,而new申請空間時是拋異常,new/delete申請和釋放的是單個元素的空間,new[]和delete[]申請的是連續空間。
1.new的原理:1.調用operator new函數申請空間。2.在申請空間上執行構造函數,完成對象的初始化。
2.delete的原理:1.在空間上執行析構函數,完成對象中資源的清理工作。2.調用operator delete函數釋放空間。
另外new T[N]的原理:調用operator new[]函數,在operator new[]中實際調用N次operator new函數完成N個對象空間的申請,然后在申請的空間上執行N次構造函數。**delete[]的原理:**在釋放的對象空間上執行N次析構函數,完成N個對象中資源的清理。然后調用operator delete[]釋放空間,實際在operator delete[]中調用N次operator delete來釋放空間。
初學者看到“delete調用析構函數,完成對象資源的清理工作,后邊又調用operator delete函數釋放空間”這部分內容時可能會比較混亂,這里以棧為例子詳細說下:
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;}
首先,創建st變量,存放在棧當中,然后調用構造函數_a申請空間(對應上圖動作1)。
接著,對于ps,會先去堆上調用operator new開辟一塊空間(對應上圖動作2),再調用構造函數對對象進行初始化,初始化時_a又會申請空間(對應上圖動作3)
最后,delete[] ps,會先調用析構函數完成對象資源的清理,即釋放_ a申請的空間,然后調用operator delete釋放ps申請的空間,然后調用析構函數 _ a申請的空間。(就是步驟321)
1.malloc/free是函數,而new/delete是操作符。
2.malloc申請的空間不會初始化,而new申請的空間可以初始化(內置類型new也不會初始化)。
3.malloc申請空間時需要手動計算要申請的空間的字節數,而new申請空間只需要所申請的類型即可。
4.malloc的返回值為void*,使用是需要強制類型轉換,而new不需要,因為new跟的是空間的類型。
5.對于申請內存失敗,malloc的處理是返回空指針NULL,而new的處理是拋異常
6.對于自定義類型,new/delete會調用其構造/析構函數,而malloc/delete不會。
內存泄漏指因為疏忽或錯誤造成程序未能釋放已經不再使用的內存的情況。內存泄漏并不是指內存在物理上的消失,而是應用程序分配某段內存后,因為設計錯誤,失去了對該段內存的控制,因而造成了內存的浪費。
如果是長期運行的程序出現內存泄漏,影響很大,如操作系統、后臺服務等等,出現內存泄漏會導致響應越來越慢,最終卡死。
比如王者榮耀后臺服務,長期運行,只有升級的時候才會停,內存泄漏會導致可用內存越來越少,程序越來越慢,甚至掛掉。
再比如物聯網設備:各種智能家居、智能機器人等等,它們內存很小,也經不起內存泄漏的折騰。
by the way,對于C++我們需要主動釋放內存,但是在Java當中,不再需要主動釋放內存,Java后臺有垃圾回收器,接管了內存釋放(所以Java寫得好舒服,嗚嗚嗚)
1.智能指針
2.內存泄漏檢測工具
2.1在linux環境下:
2.2在Windows環境下使用第三方工具:VLD工具
原理:以Visual Leak Detector為例,其工作分為3步,首先在初始化注冊一個鉤子函數;然后在內存分配時該鉤子函數被調用以記錄下當時的現場;最后檢查堆內存分配鏈表以確定是否存在內存泄漏并將泄漏內存的現場轉換成可讀的形式輸出。
感謝您的閱讀?。。?strong>如果內容對你有幫助的話,記得給我三連(點贊、收藏、關注)——做個手有余香的人。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/123952.html
摘要:但是在中就只是聲明,但是還沒有分配空間,中的才是分配了內存的。對于語言會報錯這樣是對的詳細說明原因只能是或者是沒有這個是中的語法,所以要區分和的語法和機制如果用聲明了對象會報錯這才是正確的語法 C++中創建對象的兩種語法 在c++的類中,我如果要訪問類中的成員變量或函數,有2種方法,第一種就是定義一個一個對象,如: Class A ... A aa; aa.xxx(); 另外一種就是...
閱讀 1988·2021-11-19 09:40
閱讀 1930·2021-09-28 09:36
閱讀 2279·2021-09-22 10:02
閱讀 2724·2019-08-30 14:00
閱讀 1948·2019-08-29 15:31
閱讀 2893·2019-08-29 15:11
閱讀 2905·2019-08-29 13:04
閱讀 1080·2019-08-27 10:55