標籤:oc指標 技術 應該 接受 返回 需要 清理 new 自動
一、記憶體的簡要瞭解
說到記憶體,很多人應該都多多少少有點瞭解了,我們在這再稍微多說幾句:
一般我們可以把記憶體理解為三個部分:靜態區,棧,堆。有些朋友搞不清到底什麼是棧什麼是堆,堆棧有多人會認為是堆和棧,兩個放在一塊。其實不然,其中我們口中講的堆棧就是棧,而不是堆。堆的英文是heap ;棧的英文是stack(也翻譯為堆棧)。
儲存內容:
靜態區:儲存自動全域變數和static變數(包括static全域和局部變數)。靜態區的內容在整個程式的生命週期內都存在,有編譯器在編譯的時候分配(資料區段(儲存全域資料和待用資料)和程式碼片段(可執行檔代碼/唯讀常量))。
棧:儲存局部變數。棧上的內容只在函數範圍記憶體在,當函數運行結束的時候,這些內容也會自動銷毀。其特點是效率高但是空間大小有限。
堆:由malloc系列函數或者new操作符分配的記憶體。其生命週期由free和delete決定。在沒有釋放之前一直存在,直到函數結束。其特點是使用靈活,空間比較大,但容易出錯。
我們用一張圖來簡單看下: 值得注意的一點是:程式碼片段中儲存的是可執行檔代碼和唯讀常量,很多人看到程式碼片段就認為裡面只有代碼,資料區段裡面才是儲存資料的,其實不是這樣的。
二、malloc、calloc、realloc
三者的簡單對比:
malloc 函數原型:(void*)malloc(unsigned size);(位元組數)
malloc函數在記憶體中開闢的是一塊連續的空間,size是所需要空間的長度,開闢的大小為size*參數類型,開闢完之後返回這塊空間的首地址。
calloc 函數原型:void* calloc(size_t numElements, size_t sizeOfElements);(元素的個數, 單個元素的位元組數)
和malloc相似,它也是開闢一塊連續的空間,空間的大小為:元素的個數*單個元素的位元組數。
realloc 函數原型:void* realloc(void* ptr, unsigned newsize);(地址,位元組數)
給一個已經分配地址的指標重新分配空間,參數ptr為原有的空間指標,newsize為重新申請的地址長度。它與malloc的區別就是如果你給的指標是NULL,那麼你使用的就是malloc,如果你給出的指標是一個已經分配了地址的指標(ptr),那麼你使用的就是realloc。
區別:
(1)函數malloc不能初始化所分配的空間,而函數calloc能,也就是說,如果由malloc函數分配的空間原來沒有被分配過,則其中每一位都可能是0;反之,如果這一塊資料區塊原來被分配過,那裡面可能遺留著各種各樣的資料。所以,當你在使用malloc開闢一塊新空間的時候,要重新初始化那一塊空間(一般調用memset函數來初始化空間)。否則在多次釋放、開闢之後,可能會出現使用錯誤。
(2)calloc函數會將所分配的記憶體空間中的每一位都初始化為0(這也是它和malloc的主要不同處之一)。也就是說,如果你是為字元類或者整形類的元素分配空間,那麼這些元素會保證被初始化為0;如果你是為指標類函數分配記憶體,那麼這些元素都會被初始化為空白指標。
(3)malloc向系統申請size個位元組的空間,申請完之後返回的是這個空間的首地址,類型為void*,而void*表示未確定的類型,在c/c++中void*可以被強轉成任意類型的指標。
(4)realloc可以對給定的指標所指向的空間進行擴大或者縮小,無論是擴大還是縮小,原有記憶體中的內容將保持不變(如果對於縮小之後的空間,被縮小的那部分空間內的資料還是會丟失)。realloc並不保證調整後的記憶體空間和原來的記憶體空間保持同一個地址。相反,realloc指標很可能指向一個新的地址。
(5)realloc是從堆上分配空間的,但當你進行擴大的時候,realloc會試圖從堆上現存的資料後面的那些位元組中擷取附加的位元組,如果能滿足,就剛好。但如果後面的位元組數不夠,其就會使用堆上第一個有足夠大小的自由塊,然後將現存的資料拷貝到新的位置,將老塊放回到堆上。在這個過程中,資料會被移動。也就是說,當你使用realloc的時候,資料可能被移動。
三、有關malloc的一些擴充(選自《高品質c/c++編程指南(林銳)》)
malloc
malloc的原型:(void*)malloc(int size)(int也可以是unsigned,int只是其中的一種特例)
malloc函數的傳回值是一個void類型的指標,參數為int類型資料,即申請分配的記憶體大小,單位是byte。記憶體配置成功之後,malloc函數返回這塊記憶體的首地址。你需要一個指標來接受這個地址。但是由於函數的傳回值是void*類型,所以必須強制轉換成你所接收的類型。也就是說,這塊記憶體將來要儲存什麼類型的資料。比如:
char* p = (char*)malloc(100);
在堆上面分配了100個位元組記憶體,返回這塊記憶體的首地址,把地址強制轉換成char*類型後賦給char*類型 的指標變數p。同時告訴我們這塊記憶體將來用來儲存char*類型的資料。也就是說你只能通過指標變數p來操作這塊記憶體。這塊記憶體本身並沒有名字,對它的訪問是匿名訪問。
記憶體釋放
有分配就一定有釋放。malloc對應的就是free函數。free函數只有一個參數,就是要釋放的記憶體塊的首地址。比如:free(p);
free函數做的事情:斬斷指標變數與這塊記憶體的關係。就像上面的例子一樣malloc函數開闢的這一個資料區塊空間是屬於p的,你只能通過p來訪問這一塊資料區塊空間,而free函數做的事情就是斬斷malloc和p之間的聯絡。但是p指標本身存放的地址並沒有發生變化,只是它對指標所指向的那塊記憶體已經沒有所有權了,不能對記憶體塊進行操作。而那塊記憶體塊裡面的資料也沒有被改變,只是你沒有辦法去訪問或者修改那塊資料快中的內容了。
malloc和free是一一對應的,如果malloc兩次但是只free一次就會存在記憶體流失,如果malloc一次但是free了兩次,就會出錯(第一次使用free的時候,malloc所開闢的空間就已經被釋放,第二次使用free就無記憶體空間可以釋放了,這種對記憶體的誤操作就有可能會導致程式的崩潰)。
函數的記憶體釋放完後,一定要把p指標置為NULL。為什嗎?
從上面可以看出,free掉之後p只是切斷了和記憶體空間的關係,但是p指標本身內部依舊存在一個地址,如果不把它置成空,那這個指標就會變成一個野指標(懸垂指標),遲早會出事。
例子:
1 char* p = (char*)malloc(100);2 strcpy(p, "hello");3 free(p);//可以看到這邊已經釋放了p所指向的那一塊空間,但是p本身儲存的地址並沒有改變4 if(NULL != p)//判斷不起作用,起不到防護作用5 {6 strcpy(p, "world");//p沒有分配空間,出錯。7 }
四、new/delete
前頭講了很多但好像還沒有講到c++的動態記憶體這方面。下面我們來進行一些討論。
我們知道c++是相容c的,那我們明明已經有了malloc和free來進行動態內容的管理,為什麼c++還要定義new和delete運算子來動態管理記憶體。
來看一下它們之間的區別和聯絡:
1.它們都是動態管理記憶體的入口。
2.malloc/free是c/c++標準庫的函數,new/delete是c++操作符。
3.malloc/free只是動態分配/釋放記憶體空間。而new/delete出來分配空間還會調用建構函式和解構函式進行初始化與清理。
4.malloc/free需要手動計算類型大小且會返回void*, new/delete可以自己計算類型的大小,返回對應類型的指標。
我們在c++中是允許進行重載的,那我們也可以重載一下new和delete,我在這就不做了(其實new和delete是不能重載的,即使你進行了重載,也只是重載了operator new和operator delete)。
有關operator new/operator delete operator new[]/operator delete[]
總結:
1.operator new/operator delete operator new[]/operator delete[]的用法和malloc/free一樣。
2.它們只負責分配空間/釋放空間,不會調用物件建構函數和解構函式來初始化/清理對象。
3.實際operator new/operator delete 只是malloc和free的一層封裝。
五、new和delete在記憶體中所做的事
new做的事:1.調用operator new分配空間2.調用建構函式初始化空間 delete做的事:1.調用解構函式清理對象2.調用operator delete釋放空間 new[N]做的事:1.調用operator new分配空間2.調用N次建構函式分別初始化每個對象 delete做的事:1.調用N次解構函式清理對象2.調用operator delete釋放空間 用一張圖來解釋:
c++動態記憶體管理