首頁 > 軟體

C語言的動態記憶體管理的深入瞭解

2022-02-08 19:00:22

一、動態記憶體分配

  • (1)用malloc類的函數分配記憶體;
  • (2)用這些記憶體支援應用程式;
  • (3)用free函數釋放記憶體。

記憶體的簡答來說的三大操作:分配----使用----釋放

記憶體管理指的是:分配— ----釋放

我們編寫的程式程式碼:使用

程式本質上就是處理資料,資料資訊需要存放在記憶體裡,就是用二極體表示的開斷表示二進位制數,進一步用二進位制數表示萬物:如音樂、文字、視訊、圖片、等等各種資源。

分配–釋放:為了更好的利用和回收記憶體資源,最大程度的發揮計算資源,統一作業系統來排程

動態記憶體分配其實就要談到自動記憶體分配

自動分配:記憶體其實就是我們在程式設計檔案裡定義的變數實際在執行時對映的記憶體,這部分變數的記憶體都是由系統自動管理(分配-釋放)。函數體(或者語句塊)內的變數都是分佈在函數  裡面,執行時就會把這個幀插入到函數執行棧裡,記憶體這些都由系統分撥,執行完就出棧,記憶體也被作業系統回收,一致到主函數出棧,程式退出。

動態分配:就是我們自己手動申請作業系統給我們程式分配的記憶體,記憶體區域主要在於  上,這部分資源是我們手動申請和回收的。分配到的資源是我們來操作、存放資料的地方。

實際上我們定義的變數最後也會被翻譯為地址,都是通過定址來操作變數的值(可以去看看組合語言)

#include <stdio.h>
#include <stdlib.h>
 int main(void)
{
	int*pi=(int*)malloc(sizeof(int));
	*pi=5;
	printf("*pi:%dn",*pi);
	free(pi);
 	return 0;
}

注意點

int *pi=(int)malloc((4));

  • 然而,依賴於系統所用的記憶體模型,整數的長度可能會發生變化。可移植的方法是使用sizeof操作符,這樣不管程式在哪裡執行都會返回正確的長度。
  • 使用(int)malloc(number * (sizeof(int)));*

二、動態記憶體分配函數

有幾個記憶體分配函數可以用來管理動態記憶體,雖然具體可用的函數取決於系統,但大部分系統的stdlib.h標頭檔案中都有如下函數:

malloc()

realloc()

calloc()

函數描述
malloc從堆上分配記憶體
realloc在之前分配的記憶體塊的基礎上,將記憶體重新分配為更大或者更小的部分
calloc從堆上分配記憶體並清零

1、malloc()

malloc函數從堆上分配一塊記憶體,所分配的位元組數由該函數唯一的引數指定,返回值是void指標,如果記憶體不足,就會返回NULL。此函數不會清空或者修改記憶體。

宣告:voidmalloc(size_t);

  • (1)從堆上分配記憶體;
  • (2)記憶體不會被修改或是清空;
  • (3)返回首位元組的地址。

範例用法int* pi=(int*)malloc(sizeof(int));

因為當malloc無法分配記憶體時會返回NULL,在使用它返回的指標之前先檢查NULL是不錯的做法,如下所示:

int*pi=(int*)malloc(sizeof(int));
if(pi!=NULL)
{
	//指標沒有問題
}else
{
	//無效的指標
}
  • (4)靜態、全域性指標和malloc

初始化靜態或全域性變數時不能呼叫函數。下面的程式碼宣告一個靜態變數,並試圖用malloc來初始化:

*static int pi = malloc(sizeof(int));

這樣會產生一個編譯時錯誤訊息,全域性變數也一樣。

對於靜態變數,可以通過在後面用一個單獨的語句給變數分配記憶體來避免這個問題。但是全域性變數不能用單獨的賦值語句,因為全域性變數是在函數和可執行程式碼外部宣告的,賦值語句這類程式碼必須出現在函數中:

static int *pi;
pi = malloc(sizeof(int)); 

2、realloc()

宣告:void *realloc(void *ptr,size t size);

realloc函數返回指向記憶體塊的指標。該函數接受兩個引數,第一個引數是指向原記憶體塊的指標,第二個是請求的大小。重新分配的塊大小和第一個引數參照的塊大小不同。返回值是指向重新分配的記憶體的指標。

第一個引數第二個引數行為
同malloc
非空0原記憶體塊被釋放
非空比原記憶體塊小利用當前的塊分配更小的塊
非空比原記憶體塊大要麼在當前位置要麼在其他位置分配更大的塊

堆管理器可以重用原始的記憶體塊,且不會修改其內容。不過程式繼續使用的記憶體超過了所請求的8位元組。也就是說,我們沒有修改字串以便它能裝進8位元組的記憶體塊中。在本例中,我們本應該調整字串的長度以使它能裝進重新分配的8位元組。實現這一點最簡單的辦法是將NUL賦給地址507。實際使用的記憶體超出分配的記憶體不是個好做法,應該避免。

3、calloc()

calloc函數在申請記憶體時會清空記憶體【清空記憶體的意思是將其內容置為二進位制0】

宣告: void *calloc(size_t numElements,size_t elementSize);

calloc函數會根據numElements和elementSize兩個引數的乘積來分配記憶體,並返回一個指向記憶體的第一個位元組的指標。如果不能分配記憶體,則會返回NULL。此函數最初用來輔助分配陣列記憶體。

如果numElements或elementSize為0,那麼calloc可能返回空指標。如果calloc無法分配記憶體就會返回空指標,而且全域性變數errno會設定為ENOMEM(記憶體不足),這是POSIX錯誤碼,有的系統上可能沒有。

三、用free函數釋放記憶體

有了動態記憶體分配,程式設計師可以將不再使用的記憶體返還給系統,這樣可以釋放記憶體
留作他用。通常用free函數實現這一點,該函數的原型如下:

宣告: void free(void *ptr);

指標引數應該指向由malloc類函數分配的記憶體的地址,這塊記憶體會被返還給堆。儘管指標仍然指向這塊區域,但是我們應該將它看成指向垃圾資料。稍後可能重新分配這塊區域,並將其裝進不同的資料。

要點

  • 釋放含義:指的是釋放堆上的申請記憶體,其實就是告訴堆管理器,這個資源我不用了,可以回收了
  • 本地還是保留了之前申請記憶體的地址,這個地址我們應該避免去使用,也就是置這個指標為NULL
  • 不能再去接引已釋放資源指標的值
  • 不能重複多次釋放指標指向的記憶體(free)

四、迷途指標

如果記憶體已經釋放,而指標還在參照原始記憶體,這樣的指標就稱為迷途指標。迷途指標沒有指向有效物件,有時候也稱為過早釋放
迷途指標帶來的問題:

  • 如果存取記憶體,則行為不可預期;
  • 如果記憶體不可存取,則是段錯誤;
  • 潛在的安全隱患。

造成的原因:

  • 存取已釋放的記憶體;
  • 返回的指標指向的是上次函數呼叫中的自動變數;
//第一種情況
int*pi = (int*)malloc(sizeof(int));
printf("*pi:%dn",*pi);
free(pi);
*pi = 10;
 //第二種情況
int*p1 = (int*)malloc(sizeof(int));
*p1 = 5;
int* p2;
p2 = p1;
free(p1);
*p2 = 10;//迷途指標
 //第三種情況
/*
大部分編譯器都把塊語句當做一個棧幀。tmp變數分配在棧幀上,之後在塊語句退出時會出棧。
pi指標現在指向一塊最終可能被其他活躍記錄(比如foo函數)覆蓋的記憶體區域。
圖2-13說明的就是這種情形。
*/
int *pi;
int tmp = 5;
pi = &tmp;
//這裡pi變成了迷途指標
foo();

總結

本篇文章就到這裡了,希望能夠給你帶來幫助,也希望您能夠多多關注it145.com的更多內容!      


IT145.com E-mail:sddin#qq.com