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