C/C++中的內(nèi)存管理小結(jié)
前言
我們最初熟知的內(nèi)存開辟方式:
- int val = 20: 在??臻g上開辟4個字節(jié)
- char array[10]: 在棧空間上開辟10個字節(jié)的連續(xù)空間
上述開辟空間的方式有兩個特點:
- 空間開辟大小是固定的。
- 數(shù)組在申明的時候,必須指定數(shù)組的長度,它所需要的內(nèi)存在編譯時分配。
但是對于空間的需求,不僅僅是上述的情況,有時候我們需要的空大小在程序運行時才能知道,那此時靜態(tài)的開辟空間的方式就不能滿足了,我們這時候只能試試動態(tài)內(nèi)存開辟。
這篇博客就來帶大家梳理一下C/C++中的內(nèi)存管理。
一:C/C++內(nèi)存分布
對內(nèi)存分段是計算機的管理機制
1.棧又叫堆棧,存放非靜態(tài)局部變量、函數(shù)參數(shù)和返回值等等,棧是向下增長的。,處理器的指令集中、效率高,但是分配內(nèi)存的容量有限。(函數(shù)執(zhí)行結(jié)束后這些存儲單元自動釋放)
2.內(nèi)存映射段是高效的IO映射方式,用于裝載一個共享的動態(tài)內(nèi)存庫。用戶可使用系統(tǒng)接口創(chuàng)建共享共享內(nèi)存,做進程間通信。
3.堆用于程序運行時動態(tài)內(nèi)存分配,堆是向上增長的。(一般由人為分配釋放,若沒有人為釋放則程序結(jié)束時可能由OS回收。)
4.數(shù)據(jù)段存儲全局數(shù)據(jù)、靜態(tài)數(shù)據(jù)。(程序結(jié)束后由系統(tǒng)自動釋放)
5.代碼段存儲可執(zhí)行的代碼、只讀常量。
注意:
棧區(qū)向下生長,先開辟的空間地址大于后開辟的空間地址。(int a = 10,int b = 20,&a>&b)
堆區(qū)向上生長,但是不保證后開辟的空間地址大于先開辟的空間地址,因為堆區(qū)存在人為的空間釋放。
二:C語言中的內(nèi)存管理方式
C語言提供了動態(tài)內(nèi)存函數(shù)來進行內(nèi)存的動態(tài)開辟工作:malloc、calloc、realloc、free
2.1 malloc
函數(shù)功能
void✳ malloc(size_t size以字節(jié)為單位的空間大?。?/p>
舉個栗子:int* ptr = (int*) malloc(sizeof(int)*10);
malloc向內(nèi)存申請一塊大小為size的連續(xù)可用空間,并返回指向這塊空間的指針。
函數(shù)特性
1.開辟成功,返回一個指向該空間的指針。
2.開辟失敗,返回一個NULL指針,因此malloc的返回值一定要做檢查。
3.返回值的類型是void✳,malloc函數(shù)并不知道開辟空間的數(shù)據(jù)類型,具體在使用的時候由使用者自己決定。
4.如果參數(shù)size為0,malloc的行為是標準未定義的,取決于編譯器。
2.2 calloc
函數(shù)功能
void✳ calloc(size_t num元素個數(shù),size_t size以字節(jié)為單位的空間大?。?/p>
舉個栗子:int* ptr = calloc(10,sizeof(int));
calloc向內(nèi)存為num個大小為size的元素開辟一塊連續(xù)空間,并且把空間的每個字節(jié)都初始化為0。
函數(shù)特性
1.開辟成功,返回一個指向該空間的指針。
2.開辟失敗,返回一個NULL指針,因此calloc的返回值一定要做檢查。
3.返回值的類型是void✳,calloc函數(shù)并不知道開辟空間的數(shù)據(jù)類型,具體在使用的時候由使用者自己決定。
4.calloc會在返回地址之前把申請的空間每個字節(jié)都初始化為0(calloc適用于對申請空間的內(nèi)容要求初始化的情況)
注意:對申請的空間初始化并不完全是好的事情,當我們要申請一個特別大的空間時,初始化會浪費很多很多的時間。
2.3 realloc
函數(shù)功能
void✳ realloc(void✳ ptr要調(diào)整的內(nèi)存地址,size_t size調(diào)整之后的空間大?。?/p>
舉個栗子:int* p = NULL; p = realloc(ptr,1000); if(p!=NULL)-> ptr = p;
realloc可以對動態(tài)開辟的內(nèi)存空間大小進行靈活調(diào)整。
函數(shù)特性
1.返回值為調(diào)整之后內(nèi)存空間的起始位置。
2.realloc在調(diào)整原內(nèi)存空間大小的基礎上,還會將原內(nèi)存空間中的數(shù)據(jù)移動到新的空間。
realloc在調(diào)整內(nèi)存空間時存在的兩種情況
情況一:原有空間之后有足夠大的空間
直接在原有內(nèi)存空間之后追加空間,原來空間的數(shù)據(jù)不發(fā)生變化
情況二:原有空間之后沒有足夠大的空間
在堆空間上重新找一塊合適大小的連續(xù)空間來使用,這樣函數(shù)返回的是一個新的內(nèi)存地址。
常見的動態(tài)內(nèi)存錯誤
1、對NULL指針的解引用操作。
2、對動態(tài)開辟空間越界訪問。
3、對非動態(tài)內(nèi)存使用free釋放。
4、釋放一塊動態(tài)開辟內(nèi)存的一部分。
5、對同一塊內(nèi)存多次釋放。
6、動態(tài)開辟內(nèi)存忘記釋放。
以上的錯誤都是十分常見的,因此我們在對內(nèi)存進行操作的時候一定要萬分小心。
典型內(nèi)存泄漏的例子
int main(){ int* p = (int*)malloc(sizeof(int)); p = (int*)malloc(sizeof(int)); free(p); p = NULL; }
這個例子中我們明明進行了釋放卻也造成了內(nèi)存泄漏,這是因為我們申請了兩次內(nèi)存空間,但是用同一個指針來接收,只釋放了一次,因此造成了內(nèi)存的泄漏。
進行動態(tài)的內(nèi)存分配后一定不能忘記在使用完畢后將內(nèi)存空間釋放,并且將指針賦值為NULL,這一點是十分關(guān)鍵的,否則將造成內(nèi)存泄漏和野指針,對程序造成很大的影響。
三:C++中的內(nèi)存管理方式
C語言內(nèi)存管理方式在C++中可以繼續(xù)使用,但有些地方就無能為力而且使用起來比較麻煩,因此C++又提出了自己的內(nèi)存管理方式:通過new和delete操作符進行動態(tài)內(nèi)存管理
在C++中我們使用new進行內(nèi)存的申請,用delete進行內(nèi)存的釋放。
3.1 內(nèi)置類型的內(nèi)存分配與釋放
new和malloc一樣會在堆上開辟空間同時需要我們手動進行內(nèi)存的釋放,但是new的寫法更加簡單易于理解同時我們還可以對單個申請的變量進行初始化。
舉個栗子幫助理解
#include <iostream> using namespace std; int main(){ int* a = new int;//等同于int* a = (int*)malloc(sizeof(int)); int* b = new int[10];//等同于int* b = (int*)malloc(sizeof(int) * 10); int* c = new int(10);//new還可以進行內(nèi)置類型的初始化 cout << *c << endl; delete a;//等同于free(a); delete[] b;//等同于free(b);(對于多個變量的空間釋放要用delete[]) delete c;//等同于free(c); return 0; }
3.2 自定義類型的內(nèi)存分配和釋放
針對自定義類型的內(nèi)存分配和釋放,new不但可以在分配內(nèi)存的時候手動調(diào)用指定的構(gòu)造函數(shù)還會在分配多個對象的空間時自動調(diào)用默認構(gòu)造函數(shù),delete也會自動調(diào)用析構(gòu)函數(shù),而malloc和free卻做不到這一點。因此可以理解為malloc和free分配出來的只不過是一個和類一樣大小的空間,并不能稱作是一個對象,而new和delete分配出來的才能被成為對象。
#include <iostream> #include <stdlib.h> using namespace std; class Stu{ public: Stu(){ cout << "default building" << endl; } Stu(int num, string name):_num(num), _name(name){ cout << "custom building" << endl; } ~Stu(){ cout << "destroying" << endl; } private: int _num; string _name; }; int main(){ cout << "malloc:" << endl; Stu* a = (Stu*)malloc(sizeof(Stu)); cout << "new:" << endl; Stu* b = new Stu(1, "張三"); cout << "malloc:" << endl; Stu* c = (Stu*)malloc(sizeof(Stu) * 5); cout << "new:" << endl; Stu* d = new Stu[5]; cout << "free:" << endl; free(a); cout << "delete:" << endl; delete b; cout << "free:" << endl; free(c); cout << "delete:" << endl; delete[] d; }
運行結(jié)果:
malloc:
new:
custom building
malloc:
new:
default building
default building
default building
default building
default building
free:
delete:
destroying
free:
delete:
destroying
destroying
destroying
destroying
destroying
3.3 new和delete的實現(xiàn)原理
new和delete在C++中其實被定義為兩個運算符,我們在使用這兩個運算符的時候它會在底層調(diào)用全局函數(shù)operator new和operator delete。
operator new
operator new在底層實現(xiàn)的源代碼
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
operator delete在底層實現(xiàn)的源代碼
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和operator delete在底層也是利用malloc和free分配內(nèi)存的,因此可以說new和delete不過是malloc和free的一層封裝。
針對內(nèi)置類型
如果申請的是內(nèi)置類型的空間,new和malloc,delete和free基本類似,不同的地方是:new/delete申請和釋放的是單個元素的空間,new[]和delete[]申請的是連續(xù)空間,而且new在申請空間失敗時會拋異常,malloc會返回NULL。
針對自定義類型
1.new的原理: 調(diào)用operator new申請空間,調(diào)用構(gòu)造函數(shù)完成初始化。
2.delete的原理: 調(diào)用析構(gòu)函數(shù)完成清理,調(diào)用operator delete釋放空間。
四:經(jīng)典面試題
new | delete和malloc | free的相同點和不同點
相同點:
new、delete、malloc、free都是從堆上開辟空間,并且需要用戶手動釋放。
不同點:
1.new和delete是操作符,malloc和free是函數(shù)。
2.malloc申請空間不會進行初始化,new申請空間可以初始化。
3.malloc申請空間失敗返回NULL,new申請空間失敗會拋出異常。
4.針對自定義類型,new和delete會自動調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)處理。
五:內(nèi)存泄漏
概念:內(nèi)存泄漏指因為疏忽或錯誤造成程序已經(jīng)不再使用的內(nèi)存沒有被釋放的情況。
危害:長期運行的程序出現(xiàn)內(nèi)存泄漏,會浪費空間,如操作系統(tǒng)、后臺服務等等,出現(xiàn)內(nèi)存泄漏會
導致響應越來越慢,最終卡死。
舉個栗子幫助理解:
void MemoryLeaks(){ // 1.內(nèi)存申請了忘記釋放 int* p1 = (int*)malloc(sizeof(int)); int* p2 = new int; // 2.異常安全問題 int* p3 = new int[10]; Func(); // 這里Func函數(shù)拋異常導致 delete[] p3未執(zhí)行,p3沒被釋放. delete[] p3; }
5.1 內(nèi)存泄漏的分類
堆內(nèi)存泄漏
程序執(zhí)行中依據(jù)須要分配通過malloc / calloc / realloc / new等從堆中分配的一塊內(nèi)存,
用完后必須通過調(diào)用相應的 free或者delete 刪掉。假設程序的設計錯誤導致這部分內(nèi)存沒有被釋放,那么以后這部分空間將無法再被使用,就會產(chǎn)生堆內(nèi)存泄漏。
系統(tǒng)資源泄漏
程序使用系統(tǒng)分配的資源,比方套接字、文件描述符、管道等沒有使用對應的函數(shù)釋放掉,導致系統(tǒng)
資源的浪費,嚴重可導致系統(tǒng)效能減少,系統(tǒng)執(zhí)行不穩(wěn)定,產(chǎn)生了系統(tǒng)資源泄露。
5.2 如何檢測內(nèi)存泄露
在linux下內(nèi)存泄漏檢測
valgrind、mtrace、dmalloc、memwatch、mpatrol、dbgmem、Electric Fence
在windows下內(nèi)存泄漏檢測
VLD
5.3 如何避免內(nèi)存泄漏
1.工程前期良好的設計規(guī)范,養(yǎng)成良好的編碼規(guī)范,申請的內(nèi)存空間記著匹配的去釋放。
2.采用RAII思想或者智能指針來管理資源。
5.4 如何在堆上一次申請4G空間
原因:申請失敗一般是因為進程地址空間不夠大。
解決辦法:換用64位的進程地址空間。
到此這篇關(guān)于C/C++中的內(nèi)存管理小結(jié)的文章就介紹到這了,更多相關(guān)C++ 內(nèi)存管理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
嵌入式C實戰(zhàn)項目開發(fā)技巧:對一個有規(guī)律的數(shù)組表進行位移操作的方法
今天小編就為大家分享一篇關(guān)于嵌入式C實戰(zhàn)項目開發(fā)技巧:對一個有規(guī)律的數(shù)組表進行位移操作的方法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-12-12C++關(guān)于類結(jié)構(gòu)體大小和構(gòu)造順序,析構(gòu)順序的測試詳解
這篇文章主要介紹了C++類結(jié)構(gòu)體大小和構(gòu)造順序,析構(gòu)順序的測試,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08openCV4.1.1+VS2019環(huán)境配置詳解
這篇文章主要介紹了openCV4.1.1+VS2019環(huán)境配置詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-08-08C++ 多線程編程建議之 C++ 對多線程/并發(fā)的支持(下)
這篇文章主要介紹的是 C++ 多線程編程建議之 C++ 對多線程/并發(fā)的支持的相關(guān)資料,承接前文 現(xiàn)代 C++ 對多線程/并發(fā)的支持,接下來我們看看回發(fā)生什么吧2021-10-10