C/C++中的內(nèi)存管理小結(jié)
前言
我們最初熟知的內(nèi)存開辟方式:
- int val = 20: 在棧空間上開辟4個字節(jié)
- char array[10]: 在??臻g上開辟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)存,做進(jìn)程間通信。
3.堆用于程序運行時動態(tài)內(nèi)存分配,堆是向上增長的。(一般由人為分配釋放,若沒有人為釋放則程序結(jié)束時可能由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ū)向上生長,但是不保證后開辟的空間地址大于先開辟的空間地址,因為堆區(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ù)類型,具體在使用的時候由使用者自己決定。
4.如果參數(shù)size為0,malloc的行為是標(biāo)準(zhǔn)未定義的,取決于編譯器。
2.2 calloc
函數(shù)功能
void✳ calloc(size_t num元素個數(shù),size_t size以字節(jié)為單位的空間大小)
舉個栗子: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)容要求初始化的情況)
注意:對申請的空間初始化并不完全是好的事情,當(dāng)我們要申請一個特別大的空間時,初始化會浪費很多很多的時間。
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)存空間時存在的兩種情況
情況一:原有空間之后有足夠大的空間
直接在原有內(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)行操作的時候一定要萬分小心。
典型內(nèi)存泄漏的例子
int main(){
int* p = (int*)malloc(sizeof(int));
p = (int*)malloc(sizeof(int));
free(p);
p = NULL;
}
這個例子中我們明明進(jìn)行了釋放卻也造成了內(nèi)存泄漏,這是因為我們申請了兩次內(nèi)存空間,但是用同一個指針來接收,只釋放了一次,因此造成了內(nèi)存的泄漏。
進(jìn)行動態(tài)的內(nèi)存分配后一定不能忘記在使用完畢后將內(nèi)存空間釋放,并且將指針賦值為NULL,這一點是十分關(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一樣會在堆上開辟空間同時需要我們手動進(jìn)行內(nèi)存的釋放,但是new的寫法更加簡單易于理解同時我們還可以對單個申請的變量進(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)存的時候手動調(diào)用指定的構(gòu)造函數(shù)還會在分配多個對象的空間時自動調(diào)用默認(rèn)構(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申請空間不會進(jìn)行初始化,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)、后臺服務(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è)計錯誤導(dǎo)致這部分內(nèi)存沒有被釋放,那么以后這部分空間將無法再被使用,就會產(chǎn)生堆內(nèi)存泄漏。
系統(tǒng)資源泄漏
程序使用系統(tǒng)分配的資源,比方套接字、文件描述符、管道等沒有使用對應(yīng)的函數(shù)釋放掉,導(dǎo)致系統(tǒng)
資源的浪費,嚴(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è)計規(guī)范,養(yǎng)成良好的編碼規(guī)范,申請的內(nèi)存空間記著匹配的去釋放。
2.采用RAII思想或者智能指針來管理資源。
5.4 如何在堆上一次申請4G空間
原因:申請失敗一般是因為進(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 C++算法題解LeetCode1408數(shù)組中的字符串匹配
這篇文章主要為大家介紹了C C++算法題解LeetCode1408數(shù)組中的字符串匹配示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
詳解如何在code block創(chuàng)建一個C語言的項目
這篇文章主要介紹了詳解如何在code block創(chuàng)建一個C語言的項目,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
C語言中QString與QByteArray互相轉(zhuǎn)換的方法
本文主要介紹了C語言中QString與QByteArray互相轉(zhuǎn)換的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05
C++中typedef 及其與struct的結(jié)合使用
這篇文章主要介紹了C++中typedef 及其與struct的結(jié)合使用,需要的朋友可以參考下2014-02-02
C++中為何推薦要把基類析構(gòu)函數(shù)設(shè)置成虛函數(shù)
這篇文章主要介紹了C++中為何推薦要把基類析構(gòu)函數(shù)設(shè)置成虛函數(shù)問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-12-12
C++實現(xiàn)中綴表達(dá)式轉(zhuǎn)化為后綴表達(dá)式詳解
這篇文章主要為大家詳細(xì)介紹了如何利用C++解決實現(xiàn)中綴表達(dá)式轉(zhuǎn)換為后綴表達(dá)式的問題,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03
Qt實現(xiàn)UDP多線程數(shù)據(jù)處理及發(fā)送的簡單實例
本文主要介紹了Qt實現(xiàn)UDP多線程數(shù)據(jù)處理及發(fā)送的簡單實例,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-10-10

