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