網(wǎng)站免費建站 圖標(biāo)seo全網(wǎng)營銷的方式
一.C/C++內(nèi)存分布
先來回顧一下C語言內(nèi)存分區(qū)示意圖如下:
代碼區(qū):
- 程序執(zhí)行代碼一般存放在代碼區(qū),字符串常量以及define定義的常量也可能存放在代碼區(qū)。
常量區(qū):
- ?字符串,數(shù)字等常量以及const修飾的全局變量往往存放在常量區(qū)。
全局(靜態(tài))區(qū):
- 將全局變量和靜態(tài)變量存放在全局(靜態(tài))區(qū):已初始化的全局變量和靜態(tài)變量存放在一塊區(qū)域, 未初始化的全局變量和未初始化的靜態(tài)變量存放在相鄰的另一塊區(qū)域。
堆區(qū):
-
堆區(qū)由程序員調(diào)用malloc, calloc, realloc等分配函數(shù)進(jìn)行內(nèi)存空間的分配和釋放,按內(nèi)存地址由低地址到高地址增長。
棧區(qū):
-
棧區(qū)由編譯器自動分配釋放,由操作系統(tǒng)自動管理,無須手動管理;在函數(shù)執(zhí)行時,函數(shù)內(nèi)局部變量的存儲單元都可以在棧上創(chuàng)建,函數(shù)執(zhí)行結(jié)束時這些存儲單元自動被釋放。
我們再來看下面的一段代碼和相關(guān)問題 :
int globalVar = 1;//數(shù)據(jù)段/靜態(tài)區(qū)
static int staticGlobalVar = 1;//數(shù)據(jù)段/靜態(tài)區(qū)
void Test()
{static int staticVar = 1;//數(shù)據(jù)段/靜態(tài)區(qū)int localVar = 1;//棧區(qū)int num1[10] = { 1, 2, 3, 4 };//棧區(qū)char char2[] = "abcd";//char2存放在棧區(qū),*char2代表字符串第一個元素存放在棧區(qū)const char* pChar3 = "abcd";//pChar3存放在棧區(qū),*pChar3存放在常量區(qū)int* ptr1 = (int*)malloc(sizeof(int) * 4);//ptr1是個指針存放在棧區(qū),*ptr1指向的內(nèi)容存放在堆區(qū)int* ptr2 = (int*)calloc(4, sizeof(int));int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);free(ptr1);free(ptr3);
}
小結(jié):
說明:
- 棧又叫堆棧--非靜態(tài)局部變量/函數(shù)參數(shù)/返回值等等,棧是向下增長的;
- 內(nèi)存映射段是高效的I/O映射方式,用于裝載一個共享的動態(tài)內(nèi)存庫。用戶可使用系統(tǒng)接口創(chuàng)建共享共享內(nèi)存,做進(jìn)程間通信;
- 堆用于程序運行時動態(tài)內(nèi)存分配,堆是可以上增長的;
- 數(shù)據(jù)段--存儲全局?jǐn)?shù)據(jù)和靜態(tài)數(shù)據(jù);
- 代碼段--可執(zhí)行的代碼/只讀常量。
二.C++內(nèi)存管理方式
C語言內(nèi)存管理方式在C++中可以繼續(xù)使用,但有些地方就無能為力,而且使用起來比較麻煩,因此C++又提出了自己的內(nèi)存管理方式:通過new和delete操作符進(jìn)行動態(tài)內(nèi)存管理。
2.1.new/delete操作內(nèi)置類型
new運算符
new運算符用來申請一塊連續(xù)的內(nèi)存,其格式如下:
new 數(shù)據(jù)類型 (初始化列表);
malloc()函數(shù)申請內(nèi)存時返回的是一個void*類型的指針,而new與malloc()不同,它分配一塊存儲空間并且指定了類型信息,并根據(jù)初始化列表中給出的值進(jìn)行初始化,是直接可以使用的內(nèi)存,這個過程稱之為new一個對象。 而且new動態(tài)創(chuàng)建對象時不必為該對象命名,直接指定數(shù)據(jù)類型即可。如果申請內(nèi)存成功,返回一個類型指針;如果申請內(nèi)存失敗,則返回NULL。
用new可以創(chuàng)建基本數(shù)據(jù)類型對象,也可以創(chuàng)建數(shù)組對象,其格式如下:
new 數(shù)據(jù)類型 [數(shù)組長度];
使用new創(chuàng)建數(shù)組時,后面可以加小括號(),但括號中不可以指定任何初始值,加小括號時由編譯器為其提供默認(rèn)初始值,而不加小括號時不提供任何初始值。例如:
int* pi=new int[10]();//pi所指向的數(shù)組中10個元素初始化為0
char* pc=new char[10];//pc所指向的數(shù)組中沒有提供初始值
C++雖然不允許定義長度為0的數(shù)組變量,但明確指出,調(diào)用new創(chuàng)建長度為0的數(shù)組是合法的,它返回有效的非零指針,但該指針不能進(jìn)行有效的解引用操作,因為它沒有指向任何元素,它主要的作用是用于比較運算。例如:
double *pd=new double[0];
delete運算符
用new運算符分配內(nèi)存,使用后要及時釋放以免造成內(nèi)存泄漏,C++提供了delete運算符來釋放new出來的內(nèi)存空間,其格式如下:
delete 指針名;
直接作用于指針就可以刪除由new創(chuàng)建的對象,釋放指針?biāo)赶虻膬?nèi)存空間。但在釋放數(shù)組對象時要在指針名前加上[ ],其格式如下:
delete [] 指針名;
如果缺失[ ],編譯器在編譯時不會報錯,但delete只能釋放部分空間,因此在程序運行時會出現(xiàn)內(nèi)存泄漏等問題。
int main()
{int* p1 = new int;//不會初始化:動態(tài)申請一個int類型的空間int* p3 = new int(10);//會初始化:申請一個int類型的空間,初始化為10int* p4 = new int[10];//申請10個int的數(shù)組,不會初始化int* p5 = new int[10]{ 1,2,3,4 };//會初始化delete p1;delete p3;delete [] p4;delete [] p5;//C語言版//int* p2 = (int*)malloc(sizeof(int));//if (p2 == nullptr)//{// perror("malloc fail");//}return 0;
}
調(diào)試分析:
注意:
申請和釋放單個元素的空間,使用new和delete操作符;申請和釋放連續(xù)的空間,使用new[]和delete[],要匹配起來使用。
2.2.new/delete操作自定義類型
new/delete除了可以操作內(nèi)置類型,也可以操作自定義類型。
class A
{
public:A(int a = 0): _a(a){cout << "A():" << this << endl;}//注意:不加缺省值會報錯,提示:沒有合適的默認(rèn)構(gòu)造函數(shù)可用/*A(int a): _a(a){cout << "A():" << this << endl;}*/~A(){cout << "~A():" << this << endl;}private:int _a;
};
//
//匹配使用,不要交叉,否則結(jié)果是不確定的,malloc出來的就要用free釋放,new出來的就要用delete釋放,不要混淆了
int main()
{//new/delete 和 malloc/free 最大區(qū)別是 new/delete對于【自定義類型】除了開辟空間還會調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)A* p1 = (A*)malloc(sizeof(A));A* p2 = new A(1);//調(diào)用構(gòu)造函數(shù)free(p1);delete p2;//調(diào)用析構(gòu)函數(shù)// 內(nèi)置類型幾乎是一樣的int* p3 = (int*)malloc(sizeof(int)); // Cint* p4 = new int;free(p3);delete p4;A* p5 = (A*)malloc(sizeof(A) * 10);A* p6 = new A[10];//調(diào)用10次構(gòu)造函數(shù)free(p5);delete[] p6;//調(diào)用10次析構(gòu)函數(shù)return 0;
}
運行結(jié)果:
注意:
要匹配使用,不要交叉,否則結(jié)果是不確定的,malloc
出來的就要用free
釋放,new
出來的就要用delete
釋放,不要混淆。
三.operator new與operator delete函數(shù)
new和delete是用戶進(jìn)行動態(tài)內(nèi)存申請和釋放的操作符,operator new和operator delete是系統(tǒng)提供的全局函數(shù),new在底層調(diào)用operator new全局函數(shù)來申請空間,delete在底層通過operator delete全局函數(shù)來釋放空間。如下所示:
//operator new:該函數(shù)實際通過malloc來申請空間,當(dāng)malloc申請空間成功時直接返回;申請空間失敗,嘗試執(zhí)行空間不足應(yīng)對措施,如果該應(yīng)對措施用戶設(shè)置了,則繼續(xù)申請,否則拋異常。
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{// try to allocate size bytesvoid* 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: 該函數(shù)最終是通過free來釋放空間的
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_FINALLYreturn;
}
案例:
class A
{
public:A(int a = 0): _a(a){cout << "A():" << this << endl;}//注意:不加缺省值會報錯,提示:沒有合適的默認(rèn)構(gòu)造函數(shù)可用/*A(int a): _a(a){cout << "A():" << this << endl;}*/~A(){cout << "~A():" << this << endl;}private:int _a;
};int main()
{A* a = new A(1);delete a;return 0;
}
反匯編分析:
小結(jié):
通過上述兩個全局函數(shù)的實現(xiàn)知道,operator new實際也是通過malloc來申請空間,如果malloc申請空間成功就直接返回,否則執(zhí)行用戶提供的空間不足應(yīng)對措施,如果用戶提供該措施就繼續(xù)申請,否則就拋異常。operator delete最終是通過free來釋放空間的。
提問:
既然operator new和operator delete這兩個全局函數(shù)是用malloc和free實現(xiàn)的,那我們是否可以用operator new和operator delete來實現(xiàn)malloc和free的功能??
答案:可以。
class A
{
public:A(int a = 0): _a(a){cout << "A():" << this << endl;}//注意:不加缺省值會報錯,提示:沒有合適的默認(rèn)構(gòu)造函數(shù)可用/*A(int a): _a(a){cout << "A():" << this << endl;}*/~A(){cout << "~A():" << this << endl;}private:int _a;
};int main()
{//內(nèi)置類型int* p1 = (int*)operator new(sizeof(int));int* p2 = new int;operator delete(p1);delete p2;//自定義類型A* p3 = (A*)operator new(sizeof(A));//不會調(diào)用構(gòu)造函數(shù)A* p4 = new A(1);operator delete(p3);//不會調(diào)用析構(gòu)函數(shù)delete p4;return 0;
}
四.new和delete的實現(xiàn)原理
內(nèi)置類型
如果申請的是內(nèi)置類型的空間,new和malloc,delete和free基本類似。不同的地方是:new/delete申請和釋放的是單個元素的空間,new[ ]和delete[ ]申請的是連續(xù)空間,而且new在申請空間失敗時會拋異常,malloc會返回NULL。
int main()
{//內(nèi)置類型//失敗了拋出異常//int* p1 = (int*)operator new(sizeof(int));int* p1 = new int;//失敗了返回空int* p2 = (int*)malloc(sizeof(int*));if (p2 == nullptr){perror("malloc fail");}return 0;
}
自定義類型
new的原理:
- 調(diào)用operator new函數(shù)申請空間;
- 在申請的空間上執(zhí)行構(gòu)造函數(shù),完成對象的構(gòu)造。
delete的原理:
- 在空間上執(zhí)行析構(gòu)函數(shù),完成對象中資源的清理工作;
- 調(diào)用operator delete函數(shù)釋放對象的空間。
new T[N]的原理:
- 調(diào)用operator new[]函數(shù),在operator new[]中實際調(diào)用operator new函數(shù)完成N個對象空間的申請;
- 在申請的空間上執(zhí)行N次構(gòu)造函數(shù)。
delete[]的原理:
- 在釋放的對象空間上執(zhí)行N次析構(gòu)函數(shù),完成N個對象中資源的清理;
- 調(diào)用operator delete[]釋放空間,實際在operator delete[]中調(diào)用operator delete來釋放空間。
案例一: 自定義類型
class A
{
public:A(int a = 0): _a(a){cout << "A():" << this << endl;}~A(){cout << "~A():" << this << endl;}private:int _a;
};int main()
{//自定義類型//申請空間:operator new -> 封裝malloc//調(diào)用構(gòu)造函數(shù)A* p5 = new A;//先調(diào)用析構(gòu)函數(shù)//再operator delete p5指向的空間delete p5;//申請空間:operator new[] -> operator new -> 封裝malloc//調(diào)用10次構(gòu)造函數(shù)A* p6 = new A[10];//先調(diào)用10次析構(gòu)函數(shù)//再operator delete[] p6指向的空間delete[] p6;A* p9 = new A[10];//free(p9);//delete p9;//把自定義的析構(gòu)函數(shù)屏蔽掉,則不會調(diào)用析構(gòu)函數(shù),可以運行通過,此時的編譯器也不會去多開辟4個字節(jié)的空間來存放元素個數(shù)10delete[] p9;//vs編譯器會多開4個字節(jié)的空間存放個數(shù)10,return 0;
}
解析:
案例二:自定義類型不匹配問題
class A
{
public:A(int a = 0): _a(a){cout << "A():" << this << endl;}~A(){cout << "~A():" << this << endl;}private:int _a;
};class Stack
{
public:Stack(){cout << "Stack()" << endl;_a = new int[4];_top = 0;_capacity = 4;}~Stack(){delete[] _a;_top = _capacity = 0;}private:int* _a;int _top;int _capacity;
};int main()
{//內(nèi)置類型不匹配通常不會報錯int* p7 = new int[10];free(p7);//自定義類型不匹配A* p8 = new A;//free(p8);//少調(diào)用析構(gòu)函數(shù),但由于不涉及內(nèi)存申請,通常不會報錯delete p8;Stack st;//st存放在棧上,為12字節(jié),其中存放了_a等,通過調(diào)用構(gòu)造函數(shù)為_a開辟了16字節(jié)的內(nèi)存空間,并指向了這塊空間Stack* pst = new Stack;//pst存放在棧上,為指針占4個字節(jié),new時會去堆上開辟12個字節(jié)的空間存放_a等,通過調(diào)用構(gòu)造函數(shù)為_a開辟了16字節(jié)的內(nèi)存空間,并指向了這塊空間//free(pst);//少調(diào)用析構(gòu)函數(shù),但由于涉及內(nèi)存申請,導(dǎo)致內(nèi)存泄漏,但不會報錯delete pst;//先調(diào)用析構(gòu)函數(shù)釋放_a所指向的內(nèi)存空間(16字節(jié)),再調(diào)用operator delete(pst)釋放new時在堆上開辟12個字節(jié)的空間//結(jié)論//由于 new/delete 底層實現(xiàn)機制有關(guān)聯(lián)交叉。不匹配使用時,可能有問題,可能沒問題,建議大家一定匹配使用return 0;
}
解析:
五.定位new表達(dá)式(placement-new)
定位new表達(dá)式是在已分配的原始內(nèi)存空間中調(diào)用構(gòu)造函數(shù)初始化一個對象。
使用格式:
new (place_address) type或者new (place_address) type(initializer-list)
注意:place_address必須是一個指針,initializer-list是類型的初始化列表。
使用場景:
定位new表達(dá)式在實際中一般是配合內(nèi)存池使用。因為內(nèi)存池分配出的內(nèi)存沒有初始化,所以如果是自定義類型的對象,需要使用new的定義表達(dá)式進(jìn)行顯示調(diào)構(gòu)造函數(shù)進(jìn)行初始化。
class A
{
public:A(int a = 0): _a(a){cout << "A():" << this << endl;}~A(){cout << "~A():" << this << endl;}private:int _a;
};int main()
{A aa;//p1現(xiàn)在指向的只不過是與A對象相同大小的一段空間,還不能算是一個對象,因為構(gòu)造函數(shù)沒有執(zhí)行A* p1 = (A*)malloc(sizeof(A));if (p1 == nullptr){perror("malloc fail");}//對一塊已有空間初始化--定位new//new(p1)A;//注意:如果A類的構(gòu)造函數(shù)有參數(shù)時,此處需要傳參new(p1)A(1);p1->~A();free(p1);A* p2 = (A*)operator new(sizeof(A));new(p2)A(10);p2->~A();operator delete(p2);return 0;
}
六.malloc/free和new/delete的區(qū)別
malloc/free和new/delete的共同點是:它們都是從堆上申請空間,并且需要用戶手動釋放。不同的地方是:
- malloc和free是函數(shù),new和delete是操作符;
- malloc申請的空間不會初始化,new可以初始化;
- malloc申請空間時,需要手動計算空間大小并傳遞,new只需在其后跟上空間的類型即可,如果是多個對象,[ ]中指定對象個數(shù)即可;
- malloc的返回值為void*,在使用時必須強轉(zhuǎn),new不需要,因為new后跟的是空間的類型;
- malloc申請空間失敗時,返回的是NULL,因此使用時必須判空,new不需要,但是new需要捕獲異常;
- 申請自定義類型對象時,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)存在物理上的消失,而是應(yīng)用程序分配某段內(nèi)存后,因為設(shè)計錯誤,失去了對該段內(nèi)存的控制,因而造成了內(nèi)存的浪費。
內(nèi)存泄漏的危害:長期運行的程序出現(xiàn)內(nèi)存泄漏,影響很大,如操作系統(tǒng)、后臺服務(wù)等等,出現(xiàn)內(nèi)存泄漏會導(dǎo)致響應(yīng)越來越慢,最終卡死。