歡迎您光臨本站 註冊首頁

C/C++中的內存管理小結

←手機掃碼閱讀     techdo @ 2020-05-13 , reply:0

前言
我們最初熟知的內存開闢方式:
int val = 20: 在棧空間上開闢4個字節
char array[10]: 在棧空間上開闢10個字節的連續空間
上述開闢空間的方式有兩個特點:
空間開闢大小是固定的。
數組在申明的時候,必須指定數組的長度,它所需要的內存在編譯時分配。
但是對於空間的需求,不僅僅是上述的情況,有時候我們需要的空大小在程序運行時才能知道,那此時靜態的開闢空間的方式就不能滿足了,我們這時候只能試試動態內存開闢。
這篇博客就來帶大家梳理一下C/C++中的內存管理。
一:C/C++內存分佈
對內存分段是計算機的管理機制
1.棧又叫堆棧,存放非靜態局部變量、函數參數和返回值等等,棧是向下增長的。,處理器的指令集中、效率高,但是分配內存的容量有限。(函數執行結束後這些存儲單元自動釋放)
2.內存映射段是高效的IO映射方式,用於裝載一個共享的動態內存庫。用戶可使用系統接口創建共享共享內存,做進程間通信。
3.堆用於程序運行時動態內存分配,堆是向上增長的。(一般由人為分配釋放,若沒有人為釋放則程序結束時可能由OS回收。)
4.數據段存儲全局數據、靜態數據。(程序結束後由系統自動釋放)
5.代碼段存儲可執行的代碼、只讀常量。
注意:
棧區向下生長,先開闢的空間地址大於後開闢的空間地址。(int a = 10,int b = 20,&a>&b)
堆區向上生長,但是不保證後開闢的空間地址大於先開闢的空間地址,因為堆區存在人為的空間釋放。
二:C語言中的內存管理方式
C語言提供了動態內存函數來進行內存的動態開闢工作:malloc、calloc、realloc、free
2.1 malloc
函數功能
void✳ malloc(size_t size以字節為單位的空間大小)
舉個栗子: int* ptr = (int*) malloc(sizeof(int)*10);
malloc向內存申請一塊大小為size的連續可用空間,並返回指向這塊空間的指針。
函數特性
1.開闢成功,返回一個指向該空間的指針。
2.開闢失敗,返回一個NULL指針,因此malloc的返回值一定要做檢查。
3.返回值的類型是void✳,malloc函數並不知道開闢空間的數據類型,具體在使用的時候由使用者自己決定。
4.如果參數size為0,malloc的行為是標準未定義的,取決於編譯器。
2.2 calloc
函數功能
void✳ calloc(size_t num元素個數,size_t size以字節為單位的空間大小)
舉個栗子: int* ptr = calloc(10,sizeof(int));
calloc向內存為num個大小為size的元素開闢一塊連續空間,並且把空間的每個字節都初始化為0。
函數特性
1.開闢成功,返回一個指向該空間的指針。
2.開闢失敗,返回一個NULL指針,因此calloc的返回值一定要做檢查。
3.返回值的類型是void✳,calloc函數並不知道開闢空間的數據類型,具體在使用的時候由使用者自己決定。
4.calloc會在返回地址之前把申請的空間每個字節都初始化為0(calloc適用於對申請空間的內容要求初始化的情況)
注意:對申請的空間初始化並不完全是好的事情,當我們要申請一個特別大的空間時,初始化會浪費很多很多的時間。
2.3 realloc
函數功能
void✳ realloc(void✳ ptr要調整的內存地址,size_t size調整之後的空間大小)
舉個栗子: int* p = NULL; p = realloc(ptr,1000); if(p!=NULL)-> ptr = p;
realloc可以對動態開闢的內存空間大小進行靈活調整。
函數特性
1.返回值為調整之後內存空間的起始位置。
2.realloc在調整原內存空間大小的基礎上,還會將原內存空間中的數據移動到新的空間。
realloc在調整內存空間時存在的兩種情況
情況一:原有空間之後有足夠大的空間
直接在原有內存空間之後追加空間,原來空間的數據不發生變化
情況二:原有空間之後沒有足夠大的空間
在堆空間上重新找一塊合適大小的連續空間來使用,這樣函數返回的是一個新的內存地址。
常見的動態內存錯誤
1、對NULL指針的解引用操作。
2、對動態開闢空間越界訪問。
3、對非動態內存使用free釋放。
4、釋放一塊動態開闢內存的一部分。
5、對同一塊內存多次釋放。
6、動態開闢內存忘記釋放。
以上的錯誤都是十分常見的,因此我們在對內存進行操作的時候一定要萬分小心。
典型內存洩漏的例子
int main(){ int* p = (int*)malloc(sizeof(int)); p = (int*)malloc(sizeof(int)); free(p); p = NULL; }
這個例子中我們明明進行了釋放卻也造成了內存洩漏,這是因為我們申請了兩次內存空間,但是用同一個指針來接收,只釋放了一次,因此造成了內存的洩漏。
進行動態的內存分配後一定不能忘記在使用完畢後將內存空間釋放,並且將指針賦值為NULL,這一點是十分關鍵的,否則將造成內存洩漏和野指針,對程序造成很大的影響。
三:C++中的內存管理方式
C語言內存管理方式在C++中可以繼續使用,但有些地方就無能為力而且使用起來比較麻煩,因此C++又提出了自己的內存管理方式:通過new和delete操作符進行動態內存管理
在C++中我們使用new進行內存的申請,用delete進行內存的釋放。
3.1 內置類型的內存分配與釋放
new和malloc一樣會在堆上開闢空間同時需要我們手動進行內存的釋放,但是new的寫法更加簡單易於理解同時我們還可以對單個申請的變量進行初始化。
舉個栗子幫助理解
#include

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還可以進行內置類型的初始化 cout << *c << endl; delete a;//等同於free(a); delete[] b;//等同於free(b);(對於多個變量的空間釋放要用delete[]) delete c;//等同於free(c); return 0; }
3.2 自定義類型的內存分配和釋放
針對自定義類型的內存分配和釋放,new不但可以在分配內存的時候手動調用指定的構造函數還會在分配多個對象的空間時自動調用默認構造函數,delete也會自動調用析構函數,而malloc和free卻做不到這一點。因此可以理解為malloc和free分配出來的只不過是一個和類一樣大小的空間,並不能稱作是一個對象,而new和delete分配出來的才能被成為對象。
#include#includeusing 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; }
運行結果:
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的實現原理
new和delete在C++中其實被定義為兩個運算符,我們在使用這兩個運算符的時候它會在底層調用全局函數operator new和operator delete。
operator new
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
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和operator delete在底層也是利用malloc和free分配內存的,因此可以說new和delete不過是malloc和free的一層封裝。
針對內置類型
如果申請的是內置類型的空間,new和malloc,delete和free基本類似,不同的地方是:new/delete申請和釋放的是單個元素的空間,new[]和delete[]申請的是連續空間,而且new在申請空間失敗時會拋異常,malloc會返回NULL。
針對自定義類型
1.new的原理: 調用operator new申請空間,調用構造函數完成初始化。
2.delete的原理: 調用析構函數完成清理,調用operator delete釋放空間。
四:經典面試題
new | delete和malloc | free的相同點和不同點
相同點:
new、delete、malloc、free都是從堆上開闢空間,並且需要用戶手動釋放。
不同點:
1.new和delete是操作符,malloc和free是函數。
2.malloc申請空間不會進行初始化,new申請空間可以初始化。
3.malloc申請空間失敗返回NULL,new申請空間失敗會拋出異常。
4.針對自定義類型,new和delete會自動調用構造函數和析構函數處理。
五:內存洩漏
概念:內存洩漏指因為疏忽或錯誤造成程序已經不再使用的內存沒有被釋放的情況。
危害:長期運行的程序出現內存洩漏,會浪費空間,如操作系統、後臺服務等等,出現內存洩漏會
導致響應越來越慢,最終卡死。
舉個栗子幫助理解:
void MemoryLeaks(){ // 1.內存申請了忘記釋放 int* p1 = (int*)malloc(sizeof(int)); int* p2 = new int; // 2.異常安全問題 int* p3 = new int[10]; Func(); // 這裡Func函數拋異常導致 delete[] p3未執行,p3沒被釋放. delete[] p3; }
5.1 內存洩漏的分類
堆內存洩漏
程序執行中依據須要分配通過malloc / calloc / realloc / new等從堆中分配的一塊內存,
用完後必須通過調用相應的 free或者delete 刪掉。假設程序的設計錯誤導致這部分內存沒有被釋放,那麼以後這部分空間將無法再被使用,就會產生堆內存洩漏。
系統資源洩漏
程序使用系統分配的資源,比方套接字、文件描述符、管道等沒有使用對應的函數釋放掉,導致系統
資源的浪費,嚴重可導致系統效能減少,系統執行不穩定,產生了系統資源洩露。
5.2 如何檢測內存洩露
在linux下內存洩漏檢測
valgrind、mtrace、dmalloc、memwatch、mpatrol、dbgmem、Electric Fence
在windows下內存洩漏檢測
VLD
5.3 如何避免內存洩漏
1.工程前期良好的設計規範,養成良好的編碼規範,申請的內存空間記著匹配的去釋放。
2.採用RAII思想或者智能指針來管理資源。
5.4 如何在堆上一次申請4G空間
原因:申請失敗一般是因為進程地址空間不夠大。
解決辦法:換用64位的進程地址空間。

[techdo ] C/C++中的內存管理小結已經有353次圍觀

http://coctec.com/docs/c/language/show-post-234282.html