深入浅出之动态内存(new,malloc深度分析)
| void GetMemory(char *p) { p = (char *)malloc(100); } void Test(void) { char *str = NULL; GetMemory(str);?? strcpy(str, "hello world"); printf(str); } 請問運(yùn)行Test函數(shù)會有什么樣的結(jié)果? 答:程序崩潰。 因?yàn)镚etMemory并不能傳遞動態(tài)內(nèi)存, Test函數(shù)中的?str一直都是?NULL。 strcpy(str, "hello world");將使程序崩潰。 | char *GetMemory(void) {?? char p[] = "hello world"; return p; } void Test(void) { char *str = NULL; str = GetMemory();??? printf(str); } 請問運(yùn)行Test函數(shù)會有什么樣的結(jié)果? 答:可能是亂碼。 因?yàn)镚etMemory返回的是指向“棧內(nèi)存”的指針,該指針的地址不是?NULL,但其原現(xiàn)的內(nèi)容已經(jīng)被清除,新內(nèi)容不可知。 |
| void GetMemory2(char **p, int num) { *p = (char *)malloc(num); } void Test(void) { char *str = NULL; GetMemory(&str, 100); strcpy(str, "hello");?? printf(str);??? } 請問運(yùn)行Test函數(shù)會有什么樣的結(jié)果? 答: (1)能夠輸出hello (2)內(nèi)存泄漏 | void Test(void) { char *str = (char *) malloc(100); ????strcpy(str,?“hello”); ????free(str);???? ????if(str != NULL) ????{ ?????strcpy(str,?“world”); printf(str); } } 請問運(yùn)行Test函數(shù)會有什么樣的結(jié)果? 答:篡改動態(tài)內(nèi)存區(qū)的內(nèi)容,后果難以預(yù)料,非常危險(xiǎn)。 因?yàn)閒ree(str);之后,str成為野指針, if(str != NULL)語句不起作用。 |
1.? 關(guān)鍵字、操作符與庫函數(shù)
- 關(guān)鍵字是編譯器保留的文字,不能被用戶拿來重新聲明,像const, new, if等等
- 操作符必須要有操作對象,操作符本質(zhì)上可以視為編譯器內(nèi)置的基礎(chǔ)的函數(shù)。操作符在c++中,可以被重載(除了部分例外,比如 . :: sizeof)。
- 庫函數(shù)是編寫在編譯器頭文件庫里,要包含頭文件才能調(diào)用的封裝函數(shù)。
2.?自由存儲區(qū)與堆的區(qū)別
堆(heap)是C語言和操作系統(tǒng)的術(shù)語。堆是操作系統(tǒng)所維護(hù)的一塊特殊內(nèi)存,它提供了動態(tài)分配的功能,當(dāng)運(yùn)行程序調(diào)用malloc()時就會從中分配,稍后調(diào)用free可把內(nèi)存交還。而自由存儲是C++中通過new和delete動態(tài)分配和釋放對象的抽象概念,通過new來申請的內(nèi)存區(qū)域可稱為自由存儲區(qū)。基本上,所有的C++編譯器默認(rèn)使用堆來實(shí)現(xiàn)自由存儲,也即是缺省的全局運(yùn)算符new和delete也許會按照malloc和free的方式來被實(shí)現(xiàn),這時藉由new運(yùn)算符分配的對象,說它在堆上也對,說它在自由存儲區(qū)上也正確。但程序員也可以通過重載操作符,改用其他內(nèi)存來實(shí)現(xiàn)自由存儲,例如全局變量做的對象池,這時自由存儲區(qū)就區(qū)別于堆了。我們所需要記住的就是:
堆是操作系統(tǒng)維護(hù)的一塊內(nèi)存,而自由存儲是C++中通過new與delete動態(tài)分配和釋放對象的抽象概念。堆與自由存儲區(qū)并不等價(jià)。?
3. 野指針
野指針就是指針指向的位置是不可知的(隨機(jī)的、不正確的、沒有明確限制的)指針變量在定義時如果未初始化,其值是隨機(jī)的,指針變量的值是別的變量的地址,意味著指針指向了一個地址是不確定的變量,此時去解引用就是去訪問了一個不確定的地址,所以結(jié)果是不可知的。?
4. malloc和new區(qū)別
在C++中,申請動態(tài)內(nèi)存與釋放動態(tài)內(nèi)存用new/delete 與 malloc/free都可以,new/malloc申請動態(tài)內(nèi)存,操作系統(tǒng)無法自動回收,需要對應(yīng)的delete/free釋放空間。
- malloc/free 是c語言中的庫函數(shù),需要頭文件支持;而new/delete 是c++中的關(guān)鍵字
- 使用new操作符時編譯器會根據(jù)類型信息自行計(jì)算所需要的內(nèi)存,而malloc需要顯式指出所需內(nèi)存的尺寸
-
malloc內(nèi)存分配成功時,返回的是void*,需要轉(zhuǎn)換成所需要的類型
new內(nèi)存分配成功時,返回的是對象類型的指針,類型嚴(yán)格與對象匹配,無須進(jìn)行類型轉(zhuǎn)換,故new是符合類型安全性的操作符
free釋放內(nèi)存的時候需要的是void* 類型的參數(shù)
delete釋放內(nèi)存的時候需要使用具體類型的指針
-
new操作符在分配失敗的時候會拋出bac_alloc異常
malloc在分配內(nèi)存失敗時返回NULL
-
new操作符從自由存儲區(qū)(free store)上為對象動態(tài)分配內(nèi)存空間,允許重載new/delete操作符
malloc函數(shù)從堆上動態(tài)分配內(nèi)存,malloc不允許被重載
-
new會先調(diào)用operator new函數(shù),申請足夠的內(nèi)存(通常底層使用malloc實(shí)現(xiàn))。然后調(diào)用類型的構(gòu)造函數(shù),初始化成員變量,最后返回自定義類型指針。delete先調(diào)用析構(gòu)函數(shù),然后調(diào)用operator delete函數(shù)釋放內(nèi)存(通常底層使用free實(shí)現(xiàn))。
malloc/free是庫函數(shù),只能動態(tài)的申請和釋放內(nèi)存,無法強(qiáng)制要求其做自定義類型對象構(gòu)造和析構(gòu)工作。
5.?new()和new[]的區(qū)別
- ? new()創(chuàng)建一個對象
- ? new[]創(chuàng)建一個動態(tài)數(shù)組
- 釋放方法也不一樣,new()對應(yīng)delete,new[]對應(yīng)delete[]
6.?delete和delete[]的區(qū)別
?new 分配的單個對象的內(nèi)存空間的時候用 delete,回收用 new[] 分配的一組對象的內(nèi)存空間的時候用 delete[],其實(shí)要分基本數(shù)據(jù)類型和自定義數(shù)據(jù)類型。
-
基本數(shù)據(jù)類型
基本的數(shù)據(jù)類型對象沒有析構(gòu)函數(shù),并且new 在分配內(nèi)存時會記錄分配的空間大小,則delete時能正確釋放內(nèi)存,無需調(diào)用析構(gòu)函數(shù)釋放其余指針。因此兩種方式均可。
- 自定義數(shù)據(jù)類型?
從運(yùn)行結(jié)果中我們可以看出,delete p1 在回收空間的過程中,只有 p1[0] 這個對象調(diào)用了析構(gòu)函數(shù),其它對象如 p1[1]、p1[2] 等都沒有調(diào)用自身的析構(gòu)函數(shù),這就是問題的癥結(jié)所在。如果用 delete[],則在回收空間之前所有對象都會首先調(diào)用自己的析構(gòu)函數(shù)。
????基本類型的對象沒有析構(gòu)函數(shù),所以回收基本類型組成的數(shù)組空間用 delete 和 delete[] 都是應(yīng)該可以的;但是對于類對象數(shù)組,只能用 delete[]。對于 new 的單個對象,只能用 delete 不能用 delete[] 回收空間。
????所以一個簡單的使用原則就是:new 和 delete、new[] 和 delete[] 對應(yīng)使用。?
?7. delete或者free之要將指針指為NULL
delete和free被調(diào)用后,內(nèi)存不會立即回收,指針也不會指向空,delete或free僅僅是告訴操作系統(tǒng),這一塊內(nèi)存被釋放了,可以用作其他用途。但是由于沒有重新對這塊內(nèi)存進(jìn)行寫操作,所以內(nèi)存中的變量數(shù)值并沒有發(fā)生變化,這時候就會出現(xiàn)野指針的情況。因此,釋放完內(nèi)存后,應(yīng)該把指針指向NULL
?
?delete之后,p指向的地址并沒有發(fā)生變化,而地址里面的內(nèi)容卻成了隨機(jī)數(shù),那么,我們可以得到下面這條結(jié)論
我們在刪除一個指針之后,編譯器只會釋放該指針?biāo)赶虻膬?nèi)存空間,而不會刪除這個指針本身。
此時p也就成為一個野指針。
如果不加p=NULL;程序運(yùn)行時就會掛掉,加上就不會了,對NULL空間多次釋放時沒有問題的,但是不是NULL就不可以,因?yàn)檫@段空間已經(jīng)釋放掉,不屬于本程序,不能夠取隨意的釋放。
8. 使用原則
- 原則1: 優(yōu)先使用new,delete
- 原則2: 要new和delete配對,new[]和delete [],malloc和free配對使用
- 原則3: free和delte后指針一定賦予NULL,防止成為野指針
9.C++智能指針如何指向數(shù)組
智能指針在幫助C++程序員管理動態(tài)內(nèi)存方面可謂神兵利器,但是在有些情況下我們想要對數(shù)組進(jìn)行動態(tài)內(nèi)存管理就會發(fā)現(xiàn)一個問題 咦?shared_ptr 在默認(rèn)情況下是不能指向數(shù)組的,那是為什么呢。
原因是因?yàn)槲覀兊?shared_ptr 默認(rèn)的刪除器是使用 Delete 對智能指針中的對象進(jìn)行刪除,而 delete 要求 new 時是單一指針 Delete時也應(yīng)該是指針 new時是數(shù)組 delete 也應(yīng)該用數(shù)組類型去delete
shared_ptr
所以我們?nèi)绻胱屛覀兊?share_ptr 去指向指針 我們只需要去使用一個可調(diào)用對象即可 在這種情況下比較常用的函數(shù)或者lambda表達(dá)式均可
因?yàn)橹悄苤羔槢]有重載下標(biāo)運(yùn)算符 意味著我們不能想數(shù)組那樣去使用這個指針 那怎么樣才可以使用呢
shared_ptr 有一個函數(shù)可以返回當(dāng)前智能指針的內(nèi)置指針的函數(shù) 就是成員函數(shù)get() 但是我們要注意當(dāng)智能指針指針已經(jīng)釋放內(nèi)存以后,get得到的指針就成了空懸指針
有一點(diǎn)需要注意 在我們得到get返回的指針以后,智能指針對象的引用計(jì)數(shù)其實(shí)并沒有增加
這是get的函數(shù)定義
但是如果我們delete從get得到的指針 并不會出現(xiàn)多次delete的錯誤,現(xiàn)在還不是很理解為什么
我們該如何使用從get中得到的指針呢 其實(shí)很簡單,就是我們一般的指針操作即可
? ? auto x = ptr.get();cout << *(x+i) << endl;注意!!!
其實(shí)只是C++11 中不支持而已 C++17中已經(jīng)支持
unique_ptr
相比與shared_ptr unique_ptr對于動態(tài)數(shù)組的管理就輕松多了 我們只需要直接使用即可
而且unique_ptr是重載了下標(biāo)運(yùn)算符的,意味著我們可以方便把其當(dāng)數(shù)組一樣使用
Boost C++庫
著名的Boost庫其實(shí)是支持指向數(shù)組的,使用方法與unique_ptr差不多
Boost庫是什么?
類名為boost::shared_array,定義在<boost/shared_ptr.hpp>
boost::shared_array<int> arr(new int[100]);?
?
參考文獻(xiàn):
C++智能指針如何指向數(shù)組
總結(jié)
以上是生活随笔為你收集整理的深入浅出之动态内存(new,malloc深度分析)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 视场角计算原理
- 下一篇: 怎么制作usb装系统教程 USB制作系统