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