詳解C/C++內(nèi)存管理
C/C++賦予程序員管理內(nèi)存的自由,是C/C++語言特色,雖然這引入了復雜度和危險性,但另一方面,它也增加了控制力和靈活性,是C/C++獨特之處,亦是強大之處。
C/C++內(nèi)存分布
讓我們先來看看下面這段代碼:
int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
static int staticVar = 1;
int localVar = 1;
int num1[10] = { 1, 2, 3, 4 };
char char2[] = "abcd";
char* pChar3 = "abcd";
int* ptr1 = (int*)malloc(sizeof (int)* 4);
int* ptr2 = (int*)calloc(4, sizeof(int));
int* ptr3 = (int*)realloc(ptr2, sizeof(int)* 4);
free(ptr1);
free(ptr3);
}
你知道代碼中的各個部分分別存儲在內(nèi)存中的哪一個區(qū)域嗎?

【說明】
1、棧又叫堆棧,用于存儲非靜態(tài)局部變量/函數(shù)參數(shù)/返回值等等,棧是向下增長的。
2、內(nèi)存映射段是高效的I/O映射方式,用于裝載一個共享的動態(tài)內(nèi)存庫。用戶可使用系統(tǒng)接口創(chuàng)建共享內(nèi)存,做進程間通信。
3、堆用于存儲運行時動態(tài)內(nèi)存分配,堆是向上增長的。
4、數(shù)據(jù)段又叫靜態(tài)區(qū),用于存儲全局數(shù)據(jù)和靜態(tài)數(shù)據(jù)。
5、代碼段又叫常量區(qū),用于存放可執(zhí)行的代碼和只讀常量。
順便提一下:為什么說棧是向下增長的,而堆是向上增長的?

簡單來說,在一般情況下,在棧區(qū)開辟空間,先開辟的空間地址較高,而在堆區(qū)開辟空間,先開辟的空間地址較低。
例如,下面代碼中,變量a和變量b存儲在棧區(qū),指針c和指針d指向堆區(qū)的內(nèi)存空間:
#include <iostream>
using namespace std;
int main()
{
//棧區(qū)開辟空間,先開辟的空間地址高
int a = 10;
int b = 20;
cout << &a << endl;
cout << &b << endl;
//堆區(qū)開辟空間,先開辟的空間地址低
int* c = (int*)malloc(sizeof(int)* 10);
int* d = (int*)malloc(sizeof(int)* 10);
cout << c << endl;
cout << d << endl;
return 0;
}
因為在棧區(qū)開辟空間,先開辟的空間地址較高,所以打印出來a的地址大于b的地址;在堆區(qū)開辟空間,先開辟的空間地址較低,所以c指向的空間地址小于d指向的空間地址。
注意:在堆區(qū)開辟空間,后開辟的空間地址不一定比先開辟的空間地址高。因為在堆區(qū),后開辟的空間也有可能位于前面某一被釋放的空間位置。
C語言中動態(tài)內(nèi)存管理方式
malloc、calloc、realloc和free
一、malloc
malloc函數(shù)的功能是開辟指定字節(jié)大小的內(nèi)存空間,如果開辟成功就返回該空間的首地址,如果開辟失敗就返回一個NULL。傳參時只需傳入需要開辟的字節(jié)個數(shù)。
二、calloc
calloc函數(shù)的功能也是開辟指定大小的內(nèi)存空間,如果開辟成功就返回該空間的首地址,如果開辟失敗就返回一個NULL。calloc函數(shù)傳參時需要傳入開辟的內(nèi)存用于存放的元素個數(shù)和每個元素的大小。calloc函數(shù)開辟好內(nèi)存后會將空間內(nèi)容中的每一個字節(jié)都初始化為0。
三、realloc
realloc函數(shù)可以調(diào)整已經(jīng)開辟好的動態(tài)內(nèi)存的大小,第一個參數(shù)是需要調(diào)整大小的動態(tài)內(nèi)存的首地址,第二個參數(shù)是動態(tài)內(nèi)存調(diào)整后的新大小。realloc函數(shù)與上面兩個函數(shù)一樣,如果開辟成功便返回開辟好的內(nèi)存的首地址,開辟失敗則返回NULL。
realloc函數(shù)調(diào)整動態(tài)內(nèi)存大小的時候會有三種情況:
1、原地擴。需擴展的空間后方有足夠的空間可供擴展,此時,realloc函數(shù)直接在原空間后方進行擴展,并返回該內(nèi)存空間首地址(即原來的首地址)。
2、異地擴。需擴展的空間后方?jīng)]有足夠的空間可供擴展,此時,realloc函數(shù)會在堆區(qū)中重新找一塊滿足要求的內(nèi)存空間,把原空間內(nèi)的數(shù)據(jù)拷貝到新空間中,并主動將原空間內(nèi)存釋放(即還給操作系統(tǒng)),返回新內(nèi)存空間的首地址。
3、擴充失敗。需擴展的空間后方?jīng)]有足夠的空間可供擴展,并且堆區(qū)中也沒有符合需要開辟的內(nèi)存大小的空間。結(jié)果就是開辟內(nèi)存失敗,返回一個NULL。
四、free
free函數(shù)的作用就是將malloc、calloc以及realloc函數(shù)申請的動態(tài)內(nèi)存空間釋放,其釋放空間的大小取決于之前申請的內(nèi)存空間的大小。
若還想進一步了解malloc、calloc、realloc和free,請閱讀C語言動態(tài)內(nèi)存管理。
C++中動態(tài)內(nèi)存管理方式
首先,C語言內(nèi)存管理的方式在C++中可以繼續(xù)使用。但有些地方就無能為力而且使用起來比較麻煩,因此C++又提出了自己的內(nèi)存管理方式:通過new和delete操作符進行動態(tài)內(nèi)存管理。
new和delete操作內(nèi)置類型
一、動態(tài)申請單個某類型的空間
//動態(tài)申請單個int類型的空間 int* p1 = new int; //申請 delete p1; //銷毀
其作用等價于:
//動態(tài)申請單個int類型的空間 int* p2 = (int*)malloc(sizeof(int)); //申請 free(p2); //銷毀
二、動態(tài)申請多個某類型的空間
//動態(tài)申請10個int類型的空間 int* p3 = new int[10]; //申請 delete[] p3; //銷毀
其作用等價于:
//動態(tài)申請10個int類型的空間 int* p4 = (int*)malloc(sizeof(int)* 10); //申請 free(p4); //銷毀
三、動態(tài)申請單個某類型的空間并初始化
//動態(tài)申請單個int類型的空間并初始化為10 int* p5 = new int(10); //申請 + 賦值 delete p5; //銷毀
其作用等價于:
//動態(tài)申請一個int類型的空間并初始化為10 int* p6 = (int*)malloc(sizeof(int)); //申請 *p6 = 10; //賦值 free(p6); //銷毀
四、動態(tài)申請多個某類型的空間并初始化
//動態(tài)申請10個int類型的空間并初始化為0到9
int* p7 = new int[10]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; //申請 + 賦值
delete[] p7; //銷毀
其作用等價于:
//動態(tài)申請10個int類型的空間并初始化為0到9
int* p8 = (int*)malloc(sizeof(int)* 10); //申請
for (int i = 0; i < 10; i++) //賦值
{
p8[i] = i;
}
free(p8); //銷毀
注意:申請和釋放單個元素的空間,使用new和delete操作符;申請和釋放連續(xù)的空間,使用new[ ]和delete[ ]。
new和delete操作自定義類型
對于以下自定義類型:
class Test
{
public:
Test() //構(gòu)造函數(shù)
:_a(0)
{
cout << "構(gòu)造函數(shù)" << endl;
}
~Test() //析構(gòu)函數(shù)
{
cout << "析構(gòu)函數(shù)" << endl;
}
private:
int _a;
};
一、動態(tài)申請單個類的空間
用new和delete操作符:
Test* p1 = new Test; //申請 delete p1; //銷毀
用malloc和free函數(shù):
Test* p2 = (Test*)malloc(sizeof(Test)); //申請 free(p2); //銷毀
二、動態(tài)申請多個類的空間
用new和delete操作符:
Test* p3 = new Test[10]; //申請 delete[] p3; //銷毀
用malloc和free函數(shù):
Test* p4 = (Test*)malloc(sizeof(Test)* 10); //申請 free(p4); //銷毀
注意:在申請自定義類型的空間時,new會調(diào)用構(gòu)造函數(shù),delete會調(diào)用析構(gòu)函數(shù),而malloc和free不會。
總結(jié)一下:
1、C++中如果是申請內(nèi)置類型的對象或是數(shù)組,用new/delete和malloc/free沒有什么區(qū)別。
2、如果是自定義類型,區(qū)別很大,new和delete分別是開空間+構(gòu)造函數(shù)、析構(gòu)函數(shù)+釋放空間,而malloc和free僅僅是開空間和釋放空間。
3、建議在C++中無論是內(nèi)置類型還是自定義類型的申請和釋放,盡量都使用new和delete。
operator new和operator delete函數(shù)
new和delete是用戶進行動態(tài)內(nèi)存申請和釋放的操作符,operator new和operator delete是系統(tǒng)提供的全局函數(shù),new和delete在底層是通過調(diào)用全局函數(shù)operator new和operator delete來申請和釋放空間的。
operator new和operator delete的用法和malloc和free的用法完全一樣,其功能都是在堆上申請和釋放空間。
int* p1 = (int*)operator new(sizeof(int)* 10); //申請 operator delete(p1); //銷毀
其作用等價于:
int* p2 = (int*)operator new(sizeof(int)* 10); //申請 free(p2); //銷毀
實際上,operator new的底層是通過調(diào)用malloc函數(shù)來申請空間的,當malloc申請空間成功時直接返回;若申請空間失敗,則嘗試執(zhí)行空間不足的應對措施,如果該應對措施用戶設(shè)置了,則繼續(xù)申請,否則拋異常。而operator delete的底層是通過調(diào)用free函數(shù)來釋放空間的。

注意:雖然說operator new和operator delete是系統(tǒng)提供的全局函數(shù),但是我們也可以針對某個類,重載其專屬的operator new和operator delete函數(shù),進而提高效率。
new和delete的實現(xiàn)原理
內(nèi)置類型
如果申請的是內(nèi)置類型的空間,new/delete和malloc/free基本類似,不同的是,new/delete申請釋放的是單個元素的空間,new[ ]/delete [ ]申請釋放的是連續(xù)的空間,此外,malloc申請失敗會返回NULL,而new申請失敗會拋異常。
自定義類型
new的原理
1、調(diào)用operator new函數(shù)申請空間。
2、在申請的空間上執(zhí)行構(gòu)造函數(shù),完成對象的構(gòu)造。
delete的原理
1、在空間上執(zhí)行析構(gòu)函數(shù),完成對象中資源的清理工作。
2、調(diào)用operator delete函數(shù)釋放對象的空間。
new T[N]的原理
1、調(diào)用operator new[ ]函數(shù),在operator new[ ]函數(shù)中實際調(diào)用operator new函數(shù)完成N個對象空間的申請。
2、在申請的空間上執(zhí)行N次構(gòu)造函數(shù)。
delete[ ] 的原理
1、在空間上執(zhí)行N次析構(gòu)函數(shù),完成N個對象中資源的清理。
2、調(diào)用operator delete[ ]函數(shù),在operator delete[ ]函數(shù)中實際調(diào)用operator delete函數(shù)完成N個對象空間的釋放。
定位new和表達式(placement-new)
定位new表達式是在已分配的原始內(nèi)存空間中調(diào)用構(gòu)造函數(shù)初始化一個對象。
使用格式:
new(place_address)type 或者 new(place_address)type(initializer-list)
其中place_address必須是一個指針,initializer-list是類型的初始化列表。
使用場景:
定位new表達式在實際中一般是配合內(nèi)存池使用,因為內(nèi)存池分配出的內(nèi)存沒有初始化,所以如果是自定義類型的對象,就需要使用定位new表達式進行顯示調(diào)用構(gòu)造函數(shù)進行初始化。
#include <iostream>
using namespace std;
class A
{
public:
A(int a = 0) //構(gòu)造函數(shù)
:_a(a)
{}
~A() //析構(gòu)函數(shù)
{}
private:
int _a;
};
int main()
{
//new(place_address)type 形式
A* p1 = (A*)malloc(sizeof(A));
new(p1)A;
//new(place_address)type(initializer-list) 形式
A* p2 = (A*)malloc(sizeof(A));
new(p2)A(2021);
//析構(gòu)函數(shù)也可以顯示調(diào)用
p1->~A();
p2->~A();
return 0;
}
注意:在未使用定位new表達式進行顯示調(diào)用構(gòu)造函數(shù)進行初始化之前,malloc申請的空間還不能算是一個對象,它只不過是與A對象大小相同的一塊空間,因為構(gòu)造函數(shù)還沒有執(zhí)行。
常見面試題
malloc/free和new/delete的區(qū)別?
共同點:
都是從堆上申請空間,并且需要用戶手動釋放。
不同點:
1、malloc和free是函數(shù),new和delete是操作符。
2、malloc申請的空間不會初始化,new申請的空間會初始化。
3、malloc申請空間時,需要手動計算空間大小并傳遞,new只需在其后跟上空間的類型即可。
4、malloc的返回值是void*,在使用時必須強轉(zhuǎn),new不需要,因為new后跟的是空間的類型。
5、malloc申請失敗時,返回的是NULL,因此使用時必須判空,new不需要,但是new需要捕獲異常。
6、申請自定義類型對象時,malloc/free只會開辟空間,不會調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù),而new在申請空間后會調(diào)用構(gòu)造函數(shù)完成對象的初始化,delete在釋放空間前會調(diào)用析構(gòu)函數(shù)完成空間中資源的清理。
內(nèi)存泄漏 什么是內(nèi)存泄漏,內(nèi)存泄漏的危害?
內(nèi)存泄漏:
內(nèi)存泄漏是指因為疏忽或錯誤造成程序未能釋放已經(jīng)不再使用的內(nèi)存的情況。內(nèi)存泄漏并不是指內(nèi)存在物理上的消失,而是應用程序分配某段內(nèi)存后,因為設(shè)計錯誤,失去了對該段內(nèi)存的控制,因而造成了內(nèi)存的浪費。
內(nèi)存泄漏的危害:
長期運行的程序出現(xiàn)內(nèi)存泄漏,影響很大,如操作系統(tǒng)、后臺服務(wù)等等,出現(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;
}
內(nèi)存泄漏分類?
在C/C++中我們一般關(guān)心兩種方面的內(nèi)存泄漏:
1、堆內(nèi)存泄漏(Heap Leak)
堆內(nèi)存指的是程序執(zhí)行中通過malloc、calloc、realloc、new等從堆中分配的一塊內(nèi)存,用完后必須通過調(diào)用相應的free或者delete釋放。假設(shè)程序的設(shè)計錯誤導致這部分內(nèi)容沒有被釋放,那么以后這部分空間將無法再被使用,就會產(chǎn)生Heap
Leak。
2、系統(tǒng)資源泄漏
指程序使用系統(tǒng)分配的資源,比方套接字、文件描述符、管道等沒有使用對應的函數(shù)釋放掉,導致系統(tǒng)資源的浪費,嚴重可導致系統(tǒng)效能減少,系統(tǒng)執(zhí)行不穩(wěn)定。
如何避免內(nèi)存泄漏?
1、工程前期良好的設(shè)計規(guī)范,養(yǎng)成良好的編碼規(guī)范,申請的內(nèi)存空間記住匹配的去釋放。
2、采用RALL思想或者智能指針來管理資源。
3、有些公司內(nèi)部規(guī)范使用內(nèi)部實現(xiàn)的私有內(nèi)存管理庫,該庫自帶內(nèi)存泄漏檢測的功能選項。
4、出問題了使用內(nèi)存泄漏工具檢測。
內(nèi)存泄漏非常常見,解決方案分為兩種:
1、事前預防型。如智能指針等。
2、事后查錯型。如泄漏檢測工具。
如何一次在堆上申請4G的內(nèi)存?
在堆上申請4G的內(nèi)存:
#include <iostream>
using namespace std;
int main()
{
//0xffffffff轉(zhuǎn)換為十進制就是4G
void* p = malloc(0xfffffffful);
cout << p << endl;
return 0;
}
在32位的平臺下,內(nèi)存大小為4G,但是堆只占了其中的2G左右,所以我們不可能在32位的平臺下,一次性在堆上申請4G的內(nèi)存。這時我們可以將編譯器上的win32改為x64,即64位平臺,這樣我們便可以一次性在堆上申請4G的內(nèi)存了。

以上就是C/C++內(nèi)存管理詳解的詳細內(nèi)容,更多關(guān)于C++內(nèi)存管理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C語言實現(xiàn)BMP圖像處理(彩色圖轉(zhuǎn)灰度圖)
這篇文章主要為大家詳細介紹了C語言實現(xiàn)BMP圖像處理,彩色圖轉(zhuǎn)灰度圖,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-10-10
解決gcc編譯報錯unknown type name ‘bool‘問題
這篇文章主要介紹了解決gcc編譯報錯unknown type name ‘bool‘問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07
C語言數(shù)據(jù)結(jié)構(gòu)之二叉鏈表創(chuàng)建二叉樹
這篇文章主要介紹了C語言數(shù)據(jù)結(jié)構(gòu)之?二叉鏈表創(chuàng)建二叉樹,下文我們?yōu)榱烁奖愕氖褂枚鏄浣Y(jié)構(gòu)體,可以使用?typedef?對結(jié)構(gòu)體進行命名,具體內(nèi)容需要的小伙伴可以參考一下2022-02-02
c++函數(shù)中的指針參數(shù)與地址參數(shù)區(qū)別介紹
c++函數(shù)中的指針參數(shù)與地址參數(shù)區(qū)別介紹;可供參考2012-11-11
C++函數(shù)的嵌套調(diào)用和遞歸調(diào)用學習教程
這篇文章主要介紹了C++函數(shù)的嵌套調(diào)用和遞歸調(diào)用學習教程,是C++入門學習中的基礎(chǔ)知識,需要的朋友可以參考下2015-09-09

