一、前言
不知道大家在學(xué)習(xí)C語言動態(tài)分配內(nèi)存的時候有沒有過這樣的疑問,既然系統(tǒng)可以自動幫我們分配內(nèi)存,為什么還需要我們程序員自己去分配內(nèi)存呢?
如果想要弄清楚這些問題,我們首先就要了解靜態(tài)內(nèi)存和動態(tài)內(nèi)存有什么區(qū)別,只有了解了他們兩個的區(qū)別我們才能弄懂(理解)為什么需要動態(tài)分配內(nèi)存!
今天的文章會用到以下知識點,大家可以作為了解內(nèi)容去學(xué)習(xí):靜態(tài)內(nèi)存、動態(tài)內(nèi)存、堆、棧、全局變量、指針等;
二、基礎(chǔ)知識
既然要學(xué)習(xí)內(nèi)存的相關(guān)知識,那我們就先從計算機的內(nèi)存開始本篇的講解吧!在計算機內(nèi)存一共可以分為五個區(qū)域,其中有個區(qū)域是用來存儲代碼的,我們就不再進行討論了。我們首先對這四個區(qū)域進行一個簡單的了解,方便我們后面對于內(nèi)存分配的理解。
我們首先看一張內(nèi)存的組成圖:
從上面的圖我們可以看出內(nèi)存區(qū)域大概可以分為五個部分:堆、棧、全局/靜態(tài)存儲區(qū)和常量存儲區(qū)、文字常量區(qū)。下面我們對這幾個名詞進行一下簡單的講解,心里先有個概念。
棧: 棧又叫堆棧,該區(qū)域是由編譯器自動分配自動回收的變量的存儲區(qū)。通常是用來存儲局部變量的值、函數(shù)參數(shù)值等,是向下增長的。所謂向下生長的就是,先調(diào)用的棧幀的地址比后調(diào)用的地址大,棧一般大小有幾個M左右。
堆: 就是那些由程序員通過malloc函數(shù)申請到的內(nèi)存塊,一般我們申請的內(nèi)存空間系統(tǒng)是不會幫我們釋放的(當(dāng)然有些也會由系統(tǒng)釋放掉),由我們的應(yīng)用程序去控制,一般一個malloc就要對應(yīng)一個delete/free,由程序員主動釋放。
全局區(qū)(靜態(tài)區(qū)): 全局變量和靜態(tài)變量都存儲在這塊區(qū)域,與其余變量的明顯區(qū)別就是生命周期不一樣,在程序結(jié)束時,系統(tǒng)會釋放掉。
文字常量區(qū) : 這個區(qū)域主要用來儲存一些我們定義的常量,例如下面的定義就會被存儲在文字常量區(qū):char* p = "hello word!";。該部分也是由系統(tǒng)控制,程序結(jié)束后由系統(tǒng)釋放掉。
代碼區(qū): 該區(qū)域主要用來存放程序代碼,程序結(jié)束后由系統(tǒng)釋放。
通過上面的基本概念我們已經(jīng)知道了內(nèi)存中的幾個區(qū)域,以及哪些區(qū)域是我們程序員可以手動釋放的,哪些區(qū)域是由系統(tǒng)為我們自動釋放的。
我們今天主要需要用到的是堆和棧,因為我們今天要討論的動態(tài)內(nèi)存和靜態(tài)內(nèi)存和堆棧是密切相關(guān)的。動態(tài)內(nèi)存是指在堆上分配的內(nèi)存,而靜態(tài)內(nèi)存是指在棧上分配的內(nèi)存。 這里也給大家貼出一張網(wǎng)上的圖片,便于大家理解上面的知識。
在這里插入圖片描述
了解完堆棧之后我們還有個知識需要了解就是指針,由于我對于指針的理解還不是特別透徹,所以有哪些說的不對的地方大家可以在評論區(qū)指出來,我會即時進行修改。
明明我們今天要討論的是動態(tài)內(nèi)存和靜態(tài)內(nèi)存,為什么要了解指針呢?如果你有這樣的疑問說明你對于內(nèi)存或者指針的理解還不是特別到位。指針和內(nèi)存的聯(lián)系非常緊密,沒有內(nèi)存指針也將失去意義,我們對指針進行的操作實際上就是在間接的操作內(nèi)存。但是大家需要注意指針也是有類型的,他的數(shù)據(jù)類型取決于它所指向的內(nèi)存空間的數(shù)據(jù)類型。關(guān)于指針和內(nèi)存的關(guān)系我們后面會進行詳細的講解。
三、為什么要使用動態(tài)內(nèi)存
有了上面基礎(chǔ)知識的加持,我們現(xiàn)在就可以回歸我們今天的主題來討論為什么我們需要動態(tài)內(nèi)存了!我這里先說一下我的理解,我對這個問題的答案總結(jié)出以下幾點,當(dāng)然這絕不是全部的原因,鄙人也是能力有限,只能理解到這種程度,更多的理解歡迎大家在評論區(qū)進行討論!
節(jié)省資源:用多少申請多少,不需要了及時進行釋放,這樣可以避免資源的浪費。
方便儲存大型對象:大家需要注意棧區(qū)不是無限大的,對于大型項目如果說有的變量都儲存在棧區(qū),很可能會造成棧區(qū)內(nèi)存不夠用。
方便對象的調(diào)用 :對于較大的對象我們使用動態(tài)內(nèi)存存儲時我們只需要通過指針將變量首地址傳遞出去即可,而不用將整個對象都進行傳遞。
對于上面說的三點我可以給大家舉個簡單的例子,方便大家理解:
對于第一點大家應(yīng)該很好理解,我用多少就申請多少,節(jié)省資源,但是后面兩點可能就不是很好理解了,這里給大家舉個簡單的例子:
你是一個開超市的,棧區(qū)就相當(dāng)于你的超市,但是你會發(fā)現(xiàn)如果你如果把商品都放到超市,可能你的超市會裝不下那么多貨物。于是倉庫就出現(xiàn)了,堆區(qū)就相當(dāng)于你的倉庫。這些倉庫和你的超市是分離的,如果你發(fā)現(xiàn)你進了一些商品,這些商品短時間內(nèi)也不會被完全賣出去,那你就可以把這些貨物放到你的倉庫里,而你只需要記住你倉庫的地址即可。
這樣就可以保證你的超市不會因為堆積太多商品而顯得擁擠,如果有人要買這些商品,你可以把倉庫地址告訴他,他就會直接去你倉庫拿貨。
聽過這個故事你可能更迷糊了,我下面給你梳理一下,相信你會豁然開朗!
動態(tài)申請空間,能動態(tài)確定對象所需要的內(nèi)存。
我需要多大的空間,就用多大的倉庫存放該商品。
對于大型對象的存儲,棧區(qū)容不下。
我有大量的商品,都放超市太占地方。可以放倉庫中,記住倉庫地址就行。
傳遞指針比傳遞整個對象更高效。
別人要買該商品,告訴別人我倉庫地址,不用把整個倉庫搬過去。
(感覺這個故事我還是沒有講好,表達能力欠佳)
知道了動態(tài)分配內(nèi)存的好處后我們就可以更好的理解我們?yōu)槭裁匆褂脛討B(tài)分配內(nèi)存以及何時應(yīng)該使用動態(tài)分配了,所以如果你進了幾包方便面(建了個很小的對象)那你就沒必要把方便面放到倉庫了,直接放到超市貨架上就可以了。
如果你超市比較?。ùa量比較小)那你也沒必要把東西放到倉庫了,直接放到柜臺上就可以了。所以很多問出為什么要使用動態(tài)分配內(nèi)存的主要原因是因為他現(xiàn)在還沒接觸過大型項目,或者特別大的對象,如果你做過底層驅(qū)動開發(fā)或者上位機開發(fā)的話相信你對于動態(tài)申請內(nèi)存并不會陌生的。
四、什么時候需要動態(tài)分配內(nèi)存
通過上面的故事我們大概也已經(jīng)知道什么時候我們需要使用動態(tài)分配內(nèi)存了,這里再簡單的給大家做一個總結(jié)。
1、當(dāng)你的代碼量很大,需要用到很大的數(shù)據(jù)塊來存儲對象時。2、當(dāng)你的程序中用到大數(shù)組時,你就需要用動態(tài)分配內(nèi)存。3、需要數(shù)組長度根據(jù)程序進行變化。4、想讓一個變量儲存的內(nèi)容不會因為函數(shù)的結(jié)束而被收回(有點像全局變量)
這里就不得不來討論一下“傳統(tǒng)數(shù)組”的缺點了,傳統(tǒng)數(shù)組”就是前面所使用的數(shù)組,與動態(tài)內(nèi)存分配相比,傳統(tǒng)數(shù)組主要有以下幾個缺點:
數(shù)組的長度必須事先指定,而且只能是常量,不能是變量。比如像下面這么寫就是對的:
?
int?a[5]; 而像下面這么寫就是錯的: int?length?=?5; int?a[length];??//錯誤
?
因為數(shù)組長度只能是常量,所以它的長度不能在函數(shù)運行的過程當(dāng)中動態(tài)地擴充和縮小。
對于數(shù)組所占內(nèi)存空間程序員無法手動編程釋放,只能在函數(shù)運行結(jié)束后由系統(tǒng)自動釋放,所以在一個函數(shù)中定義的數(shù)組只能在該函數(shù)運行期間被其他函數(shù)使用。
而動態(tài)內(nèi)存就不存在這個問題,因為動態(tài)內(nèi)存是由程序員手動編程釋的,所以想什么時候釋放就什么時候釋放。只要程序員不手動編程釋放,就算函數(shù)運行結(jié)束,動態(tài)分配的內(nèi)存空間也不會被釋放,其他函數(shù)仍可繼續(xù)使用它。除非是整個程序運行結(jié)束,這時系統(tǒng)為該程序分配的所有內(nèi)存空間都會被釋放。
所謂“傳統(tǒng)數(shù)組”的問題,實際上就是靜態(tài)內(nèi)存的問題。我們講傳統(tǒng)數(shù)組的缺陷實際上就是以傳統(tǒng)數(shù)組為例講靜態(tài)內(nèi)存的缺陷。本質(zhì)上講的是以前所有的內(nèi)存分配的缺陷。正因為它有這么多缺陷,所以動態(tài)內(nèi)存就變得很重要。動態(tài)數(shù)組能很好地解決傳統(tǒng)數(shù)組的這幾個缺陷。
五、如何動態(tài)分配內(nèi)存
知道了我們?yōu)槭裁匆獎討B(tài)分配內(nèi)存之后我們一起來學(xué)習(xí)以下C語言中如何進行動態(tài)分配內(nèi)存。在C語言中動態(tài)分配內(nèi)存使用的是函數(shù)malloc進行分配的。
malloc 是一個系統(tǒng)函數(shù),它是 memory allocate 的縮寫。其中memory是內(nèi)存的意思,allocate是分配的意思。顧名思義 malloc 函數(shù)的功能就是分配內(nèi)存。要調(diào)用它必須要包含頭文件
?
#?include?void?*malloc(unsigned?long?size);
?
由上面的函數(shù)原型我們可以看出malloc 函數(shù)只需要一個形參,并且該形參是整形的。函數(shù)返回值為一個指向所分配的連續(xù)空間的首地址的指針。當(dāng)函數(shù)未能成功分配存儲空間時(如內(nèi)存不足)則返回一個NULL指針。所以malloc 函數(shù)的返回值為一個指針。
由于堆區(qū)內(nèi)存也是有限的,不能無限制地分配下去,所以秉持著盡量節(jié)省資源,我們應(yīng)該在分配的內(nèi)存區(qū)域不用時,及時釋放它,以便其他的變量或程序使用。
釋放malloc 函數(shù)分配內(nèi)存的函數(shù)是free函數(shù),free函數(shù)和malloc 總是成對出現(xiàn)的。free函數(shù)的原型如下所示:
?
#?include?void?free(void?*p);
?
由上面的函數(shù)原型可以看出free函數(shù)需要一個形參,且形參的類型是一個指針。free 函數(shù)無返回值,它的功能是釋放指針變量 p 所指向的內(nèi)存單元。此時 p 所指向的那塊內(nèi)存單元將會被釋放并還給操作系統(tǒng),不再歸它使用。操作系統(tǒng)可以重新將它分配給其他變量使用。
知道了申請和釋放要用到哪些函數(shù)后我們來一起看一下我們該如何使用這些函數(shù)來申請和釋放內(nèi)存。
我們這里直接貼出malloc 函數(shù)動態(tài)分配內(nèi)存的使用語句:
?
int?*p?=?(int?*)malloc(4);
?
它的意思是:請求系統(tǒng)分配 4 字節(jié)的內(nèi)存空間,并返回第一字節(jié)的地址,然后賦給指針變量 p。當(dāng)用 malloc 分配動態(tài)內(nèi)存之后,上面這個指針變量 p 就被初始化了。
需要注意的是,函數(shù) malloc 的返回值類型為 void* 型,而指針變量 p 的類型是 int* 型,即兩個類型不一樣,那么可以相互賦值嗎?
答案是可以的,原因如下:上面語句是將 void* 型被強制類型轉(zhuǎn)換 成 int*型,但事實上可以不用轉(zhuǎn)換。C 語言中,void* 型可以不經(jīng)轉(zhuǎn)換(系統(tǒng)自動轉(zhuǎn)換)地直接賦給任何類型的指針變量(函數(shù)指針變量除外)。
所以int*p = (int*)malloc(4);就可以寫成 int*p=malloc(4);。此句執(zhí)行完之后指針變量 p 就指向動態(tài)分配內(nèi)存的首地址了。
我們知道如何申請一塊內(nèi)存了,也知道何時需要申請內(nèi)存了,下面我們就來學(xué)習(xí)一下free函數(shù)的使用。
六、如何將動態(tài)分配內(nèi)存free掉
在講解之前有一點需要提醒一下大家,free函數(shù)只能釋放堆區(qū)的空間,其他區(qū)域的空間無法使用free函數(shù)的。
下面給大家貼出一段動態(tài)申請內(nèi)存的程序,來給大家講解一下free的使用。
?
#?include?#?include? int?main(void) { ????int?*p?=?malloc(sizeof*p); ????*p?=?10; ????printf("p?=?%p ",?p); ????free(p); ????printf("p?=?%p ",?p); ????return?0; }
?
輸出結(jié)果是:
?
p?=?002C2ED0 p?=?002C2ED0
?
上面的代碼和結(jié)果可以看到釋放前后,p 所指向的內(nèi)存空間是一樣的。所以釋放后 p 所指向的仍然是那塊內(nèi)存空間。
既然指向的仍然是那塊內(nèi)存空間,那么就仍然可以往里面寫數(shù)據(jù)??墒轻尫藕笤搩?nèi)存空間已經(jīng)不屬于它了,該內(nèi)存空間可能會被分配給其他變量使用。如果其他變量在里面存放了值,而你現(xiàn)在用 p 往里面寫入數(shù)據(jù)就會把那個值給覆蓋,這樣就會造成其他程序錯誤,所以當(dāng)指針變量被釋放后,要立刻把它的指向改為 NULL。
那么,當(dāng)指針變量被釋放后,它所指向的內(nèi)存空間中的數(shù)據(jù)會怎樣呢?free 的標準行為只是表示這塊內(nèi)存可以被再分配,至于它里面的數(shù)據(jù)是否被清空并沒有強制要求。
七、結(jié)語
對于動態(tài)分配內(nèi)存今天就給大家介紹到這里,自己水平也是有限,文中可能存在表述不正確的地方,希望大家發(fā)現(xiàn)后及時在評論區(qū)指出,我會及時給大家修改。
審核編輯:湯梓紅
評論