摘要:C語言中比較重要的就是指標,它可以用來連結串列操作,談到連結串列,很多時候為此分配記憶體採用動態分配而不是靜態分配。
本文分享自華為雲社群《【雲駐共創】C語言中動態記憶體分配的本質》,作者: G-washington。
C語言是一門面向過程的、抽象化的通用程式設計語言,廣泛應用於底層開發。儘管C語言提供了許多低階處理的功能,但仍然保持著跨平臺的特性,因為C語言具有可移植性,可拓展性,可重用性等特性,促使C語言仍然在程式語言排行榜上佔據一定有利地位。而C語言中比較重要的就是指標,它可以用來連結串列操作,談到連結串列,很多時候為此分配記憶體採用動態分配而不是靜態分配。
記憶體分配的概念
通常定義變數(或物件),編譯器在編譯時都可以根據該變數(或物件)的型別知道所需記憶體空間的大小,從而系統在適當的時候為他們分配確定的儲存空間。這種記憶體分配稱為靜態儲存分配;有些操作物件只在程式執行時才能確定,這樣編譯時就無法為他們預定儲存空間,只能在程式執行時,系統根據執行時的要求進行記憶體分配,這種方法稱為動態儲存分配。所有動態儲存分配都在堆區中進行。
記憶體不是取之不盡用之不竭,4g、8g、16g是常見的電腦記憶體大小,開啟工作管理員,能看到不同的應用佔據的記憶體情況。如果一個應用程式佔了大部分記憶體,估計別的應用就資源緊張了,那這個應用可能會被解除安裝,找個節省記憶體的。
記憶體管理是計算機接近物理本質的操作,那些程式語言之下的動作,最終都要調動記憶體來實現。系統的資源不是無限的,系統上執行的程式也不是隻有這一個,忽略記憶體,就會設計出危險的、冗餘的程式碼產品,或者沒法更好的互動。
動態記憶體分配的特點
動態記憶體是相對靜態記憶體而言的。所謂動態和靜態就是指記憶體的分配方式。動態記憶體是指在堆上分配的記憶體,而靜態記憶體是指在棧上分配的記憶體。動態記憶體分配的本質就是,什麼時候需要一塊記憶體的時候,再分配這塊記憶體;當不再需要某一塊記憶體的時候,就可以把這塊記憶體釋放掉。這種靈活的記憶體分配方式,正好適合連結串列這種資料結構。
傳統陣列的缺點
陣列與動態記憶體分配相比有以下缺點:
- 陣列的長度必須事先指定,而且只能是常量,不能是變數。
- 因為陣列長度只能是常量,所以它的長度不能在函式執行的過程當中動態地擴充和縮小。
- 對於陣列所佔記憶體空間程式設計師無法手動程式設計釋放,只能在函式執行結束後由系統自動釋放,所以在一個函式中定義的陣列只能在該函式執行期間被其他函式使用。
而“傳統陣列”的問題,實際上就是靜態記憶體的問題。但是動態記憶體就不存在這個問題,因為動態記憶體是由程式設計師手動程式設計釋的,所以想什麼時候釋放就什麼時候釋放。只要程式設計師不手動程式設計釋放,就算函式執行結束,動態分配的記憶體空間也不會被釋放,其他函式仍可繼續使用它。除非是整個程式執行結束,這時系統為該程式分配的所有記憶體空間都會被釋放。
動態記憶體的申請與釋放
動態記憶體的申請與釋放主要依靠兩個函式malloc和free。malloc 是一個系統函式,它是 memory allocate 的縮寫。其中memory是“記憶體”的意思,allocate是“分配”的意思。顧名思義 malloc 函式的功能就是“分配記憶體”,要呼叫它必須要包含標頭檔案<stdlib.h>。
malloc()函式會向堆中申請一片連續的可用記憶體空間;若申請成功 ,,返回指向這片記憶體空間的指標 ,若失敗 ,則會返回NULL, 所以我們在用malloc()函式開闢動態記憶體之後, 一定要判斷函式返回值是否為NULL;返回值的型別為void*型, malloc()函式並不知道連續開闢的size個位元組是儲存什麼型別資料的 ,所以需要我們自行決定 ,方法是在malloc()前加強制轉 ,轉化成我們所需型別 ,如: (int*)malloc(sizeof(int)*n).
下面使用 malloc 函式寫一個程式,程式的功能是:呼叫被調函式,將主調函式中動態分配的記憶體中的資料放大 10 倍。
輸出結果是:*p = 100
free是釋放函式,在堆中申請的記憶體空間不會像在棧中儲存的區域性變數一樣 ,函式呼叫完會自動釋放記憶體 , 如果我們不手動釋放, 直到程式執行結束才會釋放, 這樣就可能會造成記憶體洩漏, 即堆中這片記憶體中的資料已經不再使用, 但它一直佔著這片空間, 所以當我們申請的動態記憶體不再使用時 ,一定要及時釋放 .不過需要注意的是,釋放並不是指清空記憶體空間,而是指將該記憶體空間標記為“可用”狀態,使作業系統在分配記憶體時可以將它重新分配給其他變數使用。
那麼,當指標變數被釋放後,它所指向的記憶體空間中的資料會怎樣呢?free 的標準行為只是表示這塊記憶體可以被再分配,至於它裡面的資料是否被清空並沒有強制要求。不同的編譯器處理的方式可能不一樣。我們就看一下 VC++6.0 這個編譯器是怎麼處理的:
可見在 VC++6.0 中,當指標變數被釋放後,雖然它仍然是指向那個記憶體空間的,但那個記憶體空間中的值將會被重新置一個非常小的負數。動態建立的記憶體如果不用了必須要釋放。注意,一個動態記憶體只能釋放一次。如果釋放多次程式就會崩潰,因為已經釋放了,不能再釋放第二次。
綜上所述,malloc 和 free 一定要成對存在,一一對應。有 malloc 就一定要有 free,有幾個 malloc 就要有幾個 free,與此同時,每釋放一個指向動態記憶體的指標變數後要立刻把它指向 NULL。
注意事項
1)釋放一塊記憶體的一部分是不允許的。動態分配的記憶體必須整塊一起釋放。但是,realloc函式可以縮小一塊動態分配的記憶體,有效地釋放它尾部的部分記憶體。
2)不要訪問已經被free函式釋放了的記憶體。假定對一個指向動態分配的記憶體的指標進行了複製,而且這個指標的幾份拷貝分散於程式各處。你無法保證當你使用其中一個指標時它所指向的記憶體是不是已被另一個指標釋放。還要確保程式中所有使用這塊記憶體的地方在這塊記憶體釋放之前停止對它的使用。
3)當動態分配的記憶體不再需要使用時,應該被釋放,這樣可以被重新分配使用。分配記憶體但在使用完畢後不釋放將引起記憶體洩漏(memory leak)。