C/C++內(nèi)存管理詳情
C/C++內(nèi)存管理
內(nèi)存管理是C++最令人切齒痛恨的問題,也是C++最有爭議的問題,C++高手從中獲得了更好的性能,更大的自由,C++菜鳥的收獲則是一遍一遍的檢查代碼和對C++的痛恨,但內(nèi)存管理在C++中無處不在,內(nèi)存泄漏幾乎在每個(gè)C++程序中都會(huì)發(fā)生,因此要想成為C++高手,內(nèi)存管理一關(guān)是必須要過的,除非放棄C++,轉(zhuǎn)到Java或者.NET,他們的內(nèi)存管理基本是自動(dòng)的,當(dāng)然你也放棄了自由和對內(nèi)存的支配權(quán),還放棄了C++超絕的性能。
程序員們經(jīng)常編寫內(nèi)存管理程序,往往提心吊膽。如果不想觸雷,唯一的解決辦法就是發(fā)現(xiàn)所有潛伏的地雷并且排除它們,躲是躲不了的。
1. C/C++內(nèi)存分布
在C++中,內(nèi)存分成5個(gè)區(qū)
,他們分別是堆
、棧
、自由存儲(chǔ)區(qū)
、全局/靜態(tài)存儲(chǔ)區(qū)
和常量存儲(chǔ)區(qū)
?!?strong>
- 棧:在執(zhí)行函數(shù)時(shí),函數(shù)內(nèi)局部變量的存儲(chǔ)單元都可以在棧上創(chuàng)建,函數(shù)執(zhí)行結(jié)束時(shí)這些存儲(chǔ)單元自動(dòng)被釋放。棧內(nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中,效率很高,但是分配的內(nèi)存容量有限。
- 堆:就是那些由 new分配的內(nèi)存塊,他們的釋放編譯器不去管,由我們的應(yīng)用程序去控制,一般一個(gè)new就要對應(yīng)一個(gè) delete。如果程序員沒有釋放掉,那么在程序結(jié)束后,操作系統(tǒng)會(huì)自動(dòng)回收。
- 自由存儲(chǔ)區(qū):就是那些由malloc等分配的內(nèi)存塊,他和堆是十分相似的,不過它是用free來結(jié)束自己的生命的。
- 全局/靜態(tài)存儲(chǔ)區(qū):全局變量和靜態(tài)變量被分配到同一塊內(nèi)存中,在以前的C語言中,全局變量又分為初始化的和未初始化的,在C++里面沒有這個(gè)區(qū)分了,他們共同占用同一塊內(nèi)存區(qū)。
- 常量存儲(chǔ)區(qū):這是一塊比較特殊的存儲(chǔ)區(qū),他們里面存放的是常量,不允許修改?!?br />
明確區(qū)分堆與棧
堆與棧的區(qū)分問題,似乎是一個(gè)永恒的話題,由此可見,初學(xué)者對此往往是混淆不清的,所以我決定拿他第一個(gè)開刀。
首先,我們舉一個(gè)例子
void f() { int* p=new int[5]; }
這條短短的一句話就包含了堆與棧,看到new,我們首先就應(yīng)該想到,我們分配了一塊堆內(nèi)存,那么指針p呢?他分配的是一塊棧內(nèi)存,所以這句話的意思就是:在棧內(nèi)存中存放了一個(gè)指向一塊堆內(nèi)存的指針p。在程序會(huì)先確定在堆中分配內(nèi)存的大小,然后調(diào)用operator new分配內(nèi)存,然后返回這塊內(nèi)存的首地址,放入棧中。
這里,我們?yōu)榱撕唵尾]有釋放內(nèi)存,那么該怎么去釋放呢?是delete p么?澳,錯(cuò)了,應(yīng)該是delete [ ]p,這是為了告訴編譯器:我刪除的是一個(gè)數(shù)組,編譯器就會(huì)根據(jù)相應(yīng)的Cookie信息去進(jìn)行釋放內(nèi)存的工作。
2. C語言中動(dòng)態(tài)內(nèi)存管理方式
void Test () { int* p1 = (int*) malloc(sizeof(int)); free(p1); // 1.malloc/calloc/realloc的區(qū)別是什么? int* p2 = (int*)calloc(4, sizeof (int)); int* p3 = (int*)realloc(p2, sizeof(int)*10); // 這里需要free(p2)嗎? free(p3 ); }
2.1 malloc/calloc/realloc和free
malloc
malloc分配的內(nèi)存是位于堆中的,并且沒有初始化內(nèi)存的內(nèi)容,因此基本上malloc之后,調(diào)用函數(shù)memset來初始化這部分的內(nèi)存空間.
void* malloc (size_t size);
size_t是unsigned int。
malloc:分配一塊size Byte大小的內(nèi)存空間,返回一個(gè)指向該塊內(nèi)存開始的指針,指針的類型是void
函數(shù)malloc不能初始化所分配的內(nèi)存空間,而函數(shù)calloc能.如果由malloc()函數(shù)分配的內(nèi)存空間原來沒有被使用過,則其中的每一位可能都是0;反之, 如果這部分內(nèi)存曾經(jīng)被分配過,則其中可能遺留有各種各樣的數(shù)據(jù).也就是說,使用malloc()函數(shù)的程序開始時(shí)(內(nèi)存空間還沒有被重新分配)能正常進(jìn)行,但經(jīng)過一段時(shí)間(內(nèi)存空間還已經(jīng)被重新分配)可能會(huì)出現(xiàn)問題.
realloc
realloc則對malloc申請的內(nèi)存進(jìn)行大小的調(diào)整.
void* realloc (void* ptr, size_t size);
realloc可以對給定的指針?biāo)傅目臻g進(jìn)行擴(kuò)大或者縮小,無論是擴(kuò)張或是縮小,原有內(nèi)存的中內(nèi)容將保持不變.當(dāng)然,對于縮小,則被縮小的那一部分的內(nèi)容會(huì)丟失.realloc并不保證調(diào)整后的內(nèi)存空間和原來的內(nèi)存空間保持同一內(nèi)存地址.相反,realloc返回的指針很可能指向一個(gè)新的地址.
realloc是從堆上分配內(nèi)存的.當(dāng)擴(kuò)大一塊內(nèi)存空間時(shí),realloc()試圖直接從堆上現(xiàn)存的數(shù)據(jù)后面的那些字節(jié)中獲得附加的字節(jié),如果能夠滿足,自然天下太平;如果數(shù)據(jù)后面的字節(jié)不夠,問題就出來了,那么就使用堆上第一個(gè)有足夠大小的自由塊,現(xiàn)存的數(shù)據(jù)然后就被拷貝至新的位置,而老塊則放回到堆上.這句話傳遞的一個(gè)重要的信息就是數(shù)據(jù)可能被移動(dòng).
calloc
為一個(gè)大小為num的數(shù)組分配內(nèi)存,每個(gè)元素的大小是size,把每個(gè)元素初始化為0。
void* calloc(size_t numElements, size_t sizeOfElement);
函數(shù)calloc() 會(huì)將所分配的內(nèi)存空間中的每一位都初始化為零,也就是說,如果你是為字符類型或整數(shù)類型的元素分配內(nèi)存,那么這些元素將保證會(huì)被初始化為0;如果你是為指針類型的元素分配內(nèi)存,那么這些元素通常會(huì)被初始化為空指針;如果你為實(shí)型數(shù)據(jù)分配內(nèi)存,則這些元素會(huì)被初始化為浮點(diǎn)型的零.
3. C++內(nèi)存管理方式
3.1 new/delete操作內(nèi)置類型
int *i = new int; //沒有初始值 int *j = new int(100); //初始值為100 int *iArr = new int[3]; //分配具有3個(gè)元素的數(shù)組 delete i; //釋放單個(gè)變量所占用的內(nèi)存 delete j; delete []iArr; //釋放數(shù)組所占用的內(nèi)存
3.2 new和delete操作自定義類型
class Test { public: Test() : _data(0) { cout << "Test():" << this << endl; } ~Test() { cout << "~Test():" << this << endl; } private: int _data; }; void Test2() { // 申請單個(gè)Test類型的空間 Test* p1 = (Test*)malloc(sizeof(Test)); free(p1); // 申請10個(gè)Test類型的空間 Test* p2 = (Test*)malloc(sizoef(Test) * 10); free(p2); } void Test2() { // 申請單個(gè)Test類型的對象 Test* p1 = new Test; delete p1; // 申請10個(gè)Test類型的對象 Test* p2 = new Test[10]; delete[] p2; }
從上例可看出,new調(diào)用了類Test的構(gòu)造函數(shù),而malloc只是分配了空間,并沒有調(diào)用構(gòu)造函數(shù),因此會(huì)出現(xiàn)調(diào)用Test2函數(shù)時(shí),輸出的結(jié)果具有隨機(jī)性。如果用free釋放“new創(chuàng)建的動(dòng)態(tài)對象”,那么該對象因無法執(zhí)行析構(gòu)函數(shù)而可能導(dǎo)致程序出錯(cuò)。如果用delete釋放“malloc申請的動(dòng)態(tài)內(nèi)存”,理論上講程序不會(huì)出錯(cuò),但是該程序的可讀性很差。所以new/delete必須配對使用,malloc/free也一樣。
上例中,Test為類的析構(gòu)函數(shù),對象離開作用域或被delete的時(shí)候會(huì)調(diào)用。指針p指向了一個(gè)堆上創(chuàng)建的Test對象,若用free來釋放內(nèi)存,則不會(huì)調(diào)用析構(gòu)函數(shù)
4. operator new與operator delete函數(shù)
new
和delete
操作符,operator new
和operator delete
是系統(tǒng)提供的全局函數(shù),new
在底層調(diào)用operator new
全局函數(shù)來申請空間,delete
在底層通過operator delete
全局
函數(shù)來釋放空間
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)存失敗了,這里會(huì)拋出bad_alloc 類型異常 static const std::bad_alloc nomem; _RAISE(nomem); } return (p); }
operator new
是通過malloc
來申請空間,如果malloc
申請空間成功就直接返回,否則返回NULL,如果用戶提供該措施就繼續(xù)申請,否則就拋異常。operator delete
是通過free
來釋放空間的
5. new和delete的實(shí)現(xiàn)原理
5.1、new
new操作針對數(shù)據(jù)類型的處理,分為兩種情況:
(1) 簡單數(shù)據(jù)類型(包括基本數(shù)據(jù)類型和不需要構(gòu)造函數(shù)的類型)
- 簡單類型直接調(diào)用operator new分配內(nèi)存;
- 可以通過new_handler來處理new失敗的情況;
- new分配失敗的時(shí)候不像malloc那樣返回NULL,它直接拋出異常。要判斷是否分配成功應(yīng)該用異常捕獲的機(jī)制;
(2)復(fù)雜數(shù)據(jù)類型(需要由構(gòu)造函數(shù)初始化對象)
new 復(fù)雜數(shù)據(jù)類型的時(shí)候先調(diào)用operator new,然后在分配的內(nèi)存上調(diào)用構(gòu)造函數(shù)。
5.2、delete
delete也分為兩種情況:
- 簡單數(shù)據(jù)類型(包括基本數(shù)據(jù)類型和不需要析構(gòu)函數(shù)的類型)
delete簡單數(shù)據(jù)類型默認(rèn)只是調(diào)用free函數(shù)
。 - 復(fù)雜數(shù)據(jù)類型(需要由析構(gòu)函數(shù)銷毀對象)
delete復(fù)雜數(shù)據(jù)類型先調(diào)用析構(gòu)函數(shù)再調(diào)用operator delete
。
5.3、new 數(shù)組
new[]也分為兩種情況:
- 簡單數(shù)據(jù)類型(包括基本數(shù)據(jù)類型和不需要析構(gòu)函數(shù)的類型)
針對簡單類型
,new[]計(jì)算好大小后調(diào)用operator new。 - 復(fù)雜數(shù)據(jù)類型(需要由析構(gòu)函數(shù)銷毀對象)
針對復(fù)雜類型
,new[]會(huì)額外存儲(chǔ)數(shù)組大小。
5.4、delete 數(shù)組
delete[]也分為兩種情況:
- 簡單數(shù)據(jù)類型(包括基本數(shù)據(jù)類型和不需要析構(gòu)函數(shù)的類型)
針對簡單類型
,delete和delete[]等同。 - 復(fù)雜數(shù)據(jù)類型(需要由析構(gòu)函數(shù)銷毀對象)
針對復(fù)雜類型
,new[]出來的內(nèi)存只能由delete[]釋放。
到此這篇關(guān)于C/C++內(nèi)存管理詳情的文章就介紹到這了,更多相關(guān)C/C++內(nèi)存管理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++高級(jí)數(shù)據(jù)結(jié)構(gòu)之二叉查找樹
這篇文章主要介紹了C++高級(jí)數(shù)據(jù)結(jié)構(gòu)之二叉查找樹,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-05-05C語言數(shù)據(jù)結(jié)構(gòu) 棧的基礎(chǔ)操作
這篇文章主要介紹了C語言數(shù)據(jù)結(jié)構(gòu) 棧的基礎(chǔ)操作的相關(guān)資料,需要的朋友可以參考下2017-05-05C++ 中 const和static readonly區(qū)別
這篇文章主要介紹了C++ 中 const和static readonly區(qū)別的相關(guān)資料,需要的朋友可以參考下2017-05-05