首頁 > 軟體

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

2022-07-15 14:04:37

一、為什麼會存在動態記憶體

int data=20;//在棧空間上開闢4個位元組空間
char ch[5]={0};//在棧開闢5個位元組連續空間

上面展示的即為我們正常開闢固定的記憶體空間,它有兩個方面的特點

1.記憶體空間所佔大小是固定的,不能改變的。

2.建立陣列時,必須指明長度大小,在編譯時記憶體進行分配。

很顯然靜態分配記憶體分配在一些場景,就暴露出它的弊端。如果在開發之前,我們不知道空間的需求,我們有時只有在程式執行的時候才能知道自己所需要空間大小,這時候我們只能使用動態分配記憶體了。

二、動態記憶體函數

1.malloc和free

malloc函數的引數只有一個size_t size,向記憶體申請一塊連續可用的空間,有幾點需要注意

1.如果開闢成功的話,返回指向開闢好空間的指標

2.如果開闢失敗的話,則返回NULL,因此每次開闢空間之後,都要進行檢查

3.malloc函數未定義返回型別,一切由使用者自己使用

4.需參照stdlib.h標頭檔案

free函數是和malloc配套使用的,每次在堆開闢動態空間後,程式結束之前,必須進行空間釋放,不然會出現動態空間洩露,在使用free時,仍需要注意幾點

1.如果指標指向的空間不是動態開闢的,不能用free進行釋放

2.如果指標指向的是null指標,則free函數什麼事都不做

3.free不能多次使用

4.需參照stdlib.h標頭檔案

程式碼如下(範例):

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
	int* src = NULL;
	src = (int*)malloc(40);//開闢40位元組動態記憶體
	if (src == NULL)
	{
		printf("%s", strerror(errno));
		return 1;
	}
	free(src);//進行動態記憶體釋放
	src = NULL;
	return 0;
}

相信有人會問,不是已經對動態記憶體進行釋放,為什麼還要令指標等於NULL,我們偵錯一把。

這裡我們可以發現,雖然動態記憶體進行free釋放,但指標仍然指向被釋放的動態記憶體的地址,如果不置空,就會造成野指標,非法存取的問題。

2.calloc

calloc和malloc最大的區別就是,malloc只負責對記憶體進行動態開闢,但calloc不僅開闢,還進行初始化。

程式碼如下(範例):

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
	int* src = (int*)calloc(10, sizeof(int));
	if (src == NULL)
		{
	      printf("%s", strerror(errno));
	      return 1;
		}
	free(src);//進行動態記憶體釋放
	src = NULL;
	return 0;
}

我們偵錯一把可以發現,calloc在開闢空間時同時進行了初始化。所以如何我們對申請的記憶體空間的內容要求初始化,那麼可以很方便的使用calloc函數來完成任務。

3.realloc

當我們一次開闢動態記憶體不夠大的時候,realloc讓動態記憶體更加的靈活。realloc幾個引數:

1.第一個引數為要調整記憶體的地址

2.調整後大小

3.調整後記憶體的起始位置

為什麼還要返回撥整後記憶體的地址,不是直接就開闢好了嗎?其實reallloc函數在開闢時有以下兩種情況:

1.原來的記憶體之後空間是足夠的,則直接開闢

2.原來的記憶體之後空間不夠用。

我們畫圖刨析一下

情況1:直接追加空間,原來資料不變

情況2:沒有足夠的空間,在堆上找一個大小合適的連續空間。所以函數返回的是一個新的記憶體地址。

程式碼如下(範例)

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
	int* src = NULL;
	src = (int*)malloc(40);//開闢40位元組動態記憶體
	if (src == NULL)
	{
		printf("%s", strerror(errno));
		return 1;
	}
	src = realloc(src, 80);
	if (src == NULL)
	{
		printf("%s", strerror(errno));
		return 1;
	}
	free(src);//進行動態記憶體釋放
	src = NULL;
	return 0;
}

三、動態記憶體函數常見錯誤

1.動態記憶體越界存取

void test1()
{
	int* src = (int*)malloc(20);
	if (NULL == src)
	{
		return 1;
	}
	int i = 0;
	for (i = 0; i < 6; i++)
	{
		(*src+i)=i
	}
	free(src);
	src = NULL;
}

在這裡我們malloc只開闢了20個位元組,但(*src+5)造成了越界存取

2.對NULL指標進行解除參照操作

void test2()
{
	int* src = (int*)malloc(INT_MAX);//此處INT_MAX為int的最大值
	*src = 10;//如果src是NULL時,無法解除參照
	free(src);
	return 0;
}

這裡未對開闢的動態記憶體空間進行是否為空的判斷,當為空時,解除參照就會出現錯誤。

3.使用free釋放一塊動態開闢記憶體的一部分

void test3()
{
	int* src = (int*)malloc(40);
	int i=0;
	for(i=0;i<6;i++)
	{
	*(src+i)=i;
	src++;
	}
	free(src);//此時src不指向起始位置
}

因為指標指向的地址發生變化,不在指向起始未知,進行free釋放是非常危險的。

4.對靜態記憶體進行free釋放

void test4()
{
	int a = 20;
	int* src = &a;
	free(src);
}

5.對同一記憶體空間多次釋放

void test5()
{
	int* src = (int*)malloc(40);
	free(src);
	free(src);//多次釋放
}

第一個free已經將堆空間的動態記憶體進行釋放,此時src已經是一個野指標,在進行釋放是十分危險的。

6.動態開闢空間忘記釋放

void test6()
{
	int* src = (int*)malloc(40);
	if (src != NULL)
	{

	}
	while (1);
}

在開闢動態記憶體之後,一直進行while迴圈,為進行free釋放,會造成記憶體漏失。

四、經典筆試題

1.筆試1

void test(char* src)
{
	src = (char*)malloc(30);
}
int main()
{
	char* src = NULL;
	test(src);
	strcpy(src, "wo yao jin da chang");
	printf(src);
	free(src);
}

這裡會輸出wo yao jin da chang 嗎?

這裡很明顯,src仍然是NULL,所以無法輸出wo yao jin da chang

2.筆試2

char* test()
{
	char arr[] = "wo yao jin da chang";
	return arr;
}
int main()
{
	char* src = NULL;
	src = tset();
	printf(src);
	return 0;
}

這裡會輸出wo yao jin da chang 嗎?

這裡test函數確實把字串地址傳給了src,但是字串是區域性變數,當函數執行完之後,就銷燬了,所以src輸出的內容是隨機的。

3.筆試3

void test()
{
	char* src = (char*)malloc(50);
	if (src != NULL)
	{
		strcpy(src, "wo yao jin da chang");
	}
	free(src);
	if (src != NULL)
	{
		strcpy(src, "taijuanlebujinle");
		printf(src);
	}
}

這裡會輸出taijuanlebujinle 嗎?

這裡對動態記憶體釋放後,繼續進行賦值,對野指標進行了存取是錯誤的。

總結

看到這裡大家對動態記憶體管理已經有了一定的認識,應該特別注意這幾點,在進行動態記憶體開闢之後進行判斷是否為空,使用完後進行free釋放,並且置空,防止動態記憶體洩露,只要記住這幾點基本就可以很好的使用動態記憶體了。

以上就是深入瞭解C語言的動態記憶體管理的詳細內容,更多關於C語言動態記憶體管理的資料請關注it145.com其它相關文章!


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