C/C++中的內(nèi)存管理小結(jié)
前言
我們最初熟知的內(nèi)存開辟方式:
- int val = 20: 在棧空間上開辟4個字節(jié)
- char array[10]: 在??臻g上開辟10個字節(jié)的連續(xù)空間
上述開辟空間的方式有兩個特點(diǎn):
- 空間開辟大小是固定的。
- 數(shù)組在申明的時(shí)候,必須指定數(shù)組的長度,它所需要的內(nèi)存在編譯時(shí)分配。
但是對于空間的需求,不僅僅是上述的情況,有時(shí)候我們需要的空大小在程序運(yùn)行時(shí)才能知道,那此時(shí)靜態(tài)的開辟空間的方式就不能滿足了,我們這時(shí)候只能試試動態(tài)內(nèi)存開辟。
這篇博客就來帶大家梳理一下C/C++中的內(nèi)存管理。
一:C/C++內(nèi)存分布
對內(nèi)存分段是計(jì)算機(jī)的管理機(jī)制

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

