1. 程式人生 > >動態記憶體管理詳解

動態記憶體管理詳解

C語言中開闢記憶體有很多種方式,目前我們最常用的也就是陣列,但陣列是在我們用到他之前就得設定好它的長度,有時很不方便。
我們知道,c語言規定,不允許設定一個未知長度的陣列但在Linux下可以設定,但也不支援這樣做
下面這段程式碼就會報錯喲!!!

	int x = 5;
	int arr[x];

 所以,為了填補這一缺口,c語言有了動態記憶體,c語言提供了幾個函式來管理我們的動態記憶體,這幾個函式非常重要,分別為:

一:
1:malloc

void* malloc(size_t size); 

 可以看到這個函式的返回型別為void*,為一個空指標(我們前面瞭解到,void*可以作為返回值和傳參,但不能直接解引用,所以我們在運用它時需要先將它強制轉化為我們想要的指標型別

);
這個函式向記憶體申請一塊連續可用的空間,並返回指向這塊空間的指標;
如果開闢成功,則返回一個指向這塊空間的地址;
如果開闢失敗,則返回NULL,因此對malloc函式的返回值一定要先檢查再使用;

我們知道,以前我們定義的區域性變數、函式、陣列所需要的空間都是在棧上開闢的,但切記malloc申請的記憶體是在堆上開闢的棧上的可用資源遠遠小於堆上的,所以malloc的專長是用來提供大記憶體需求,而且方便管理
(這裡提到的棧和堆是什麼,我們可將計算機的記憶體結構畫成如下以便我們理解)

 

 

再提供一張棧空間的結構示意圖:

 可以看到,堆空間是向上生長的

,當我們用malloc函式開闢空間時,就會在堆上開闢一段連續的記憶體空間。


2.free

void free(void* ptr);

有了開闢記憶體,則就必須要有釋放記憶體!!!!!!!!!!!!
c語言提供了這樣一個函式來釋放我們所開闢的記憶體;

注意:如果ptr不是指向動態記憶體的,則這種行為未定義;如果ptr為NULL,則函式什麼都不會做;

下面來看一個簡單的動態記憶體開闢例子:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* ptr = NULL;
	int num = 0;
	scanf("%d", &num);
	ptr = (int*)malloc(num * sizeof(int));
	if (NULL != ptr)   //必須判斷
	{
		int i = 0;
		for (i = 0; i < num; i++)
		{
			*(ptr + i) = i;
		}
	}
	else
	{
		perror("malloc");
	}
	int* p = ptr;
	for (; p < ptr + num; p++)
	{
		printf("%d\n", *p);
	}

	free(ptr);      //勿忘
	ptr = NULL;    //有必要這樣做,不然ptr成了野指標;
	return 0;
}

perror函式是一個自動生成錯誤資訊的函式;可以自己去c庫裡檢視。
 


3.calloc

void* calloc(size_t num, size_t size);

該函式的功能為為num個大小為size的元素開闢一塊空間,並且把空間的每個位元組初始化為
該函式與malloc函式基本上沒有啥區別,唯一區別就是可以把開闢的記憶體自動初始化;初始化每個位元組為0

所以如果要求申請記憶體需要初始化,這個函式就很方便。


4.realloc

void* realloc(void* ptr, size_t size);

當我們申請的記憶體需要擴充套件或者縮減時,怎麼辦呢?
這時,c語言提供了realloc這個函式,讓動態記憶體管理變得更加靈活;

引數介紹:
ptr是要調整的記憶體地址,size為調整之後的新大小;
返回值為調整之後的記憶體起始位置;
這個函式調整原記憶體空間大小的基礎上,還會將原來記憶體中的資料移動到新的空間;

realloc在調整記憶體時存在兩種情況:
1.當原有空間的後面有足夠大的空間的時候  : 要擴充套件記憶體就直接原有記憶體之後直接追加空間,原來空間的資料不發生變化;
2.當原有空間的後面沒有足夠空間時 : 在堆空間上另找一個合適大小的連續空間來 使用。這樣函式返回的是一個新的記憶體地址;

來看一個例子:

#include<stdio.h>
int main()
{
	int* ptr = malloc(100);
	if (ptr != NULL)
	{
		;//執行任務
	}
	else
	{
		perror("malloc");
		//或者 exit(EXIT_FAILURE);
	}
	//第一種方法擴充套件
	//ptr = realloc(ptr, 1000);     //第一種方法;(可能會造成記憶體洩漏)

	//第二種方法擴充套件
	int *p = realloc(ptr, 1024);
	if (p != NULL)
	{
		ptr = p;
	}
	else
	{
		perror("realloc");
	}
	//執行任務

	free(ptr);
	return 0;
}

我們在使用realloc時,一定要判斷他返回的值,不然如果開闢失敗,則會返回NULL,第一種方法的話,ptr就指向了NULL,原來的指標就找不到了,free不掉了,就造成了記憶體洩漏;


二:我們知道,只要開闢記憶體就會有限制,我們來寫一個程式碼檢視自己電腦大概最多能開闢多少棧上記憶體;

int main()
{
	int* ptr = NULL;
	int num = 0;
	scanf("%d", &num);
	ptr = (int*)malloc(num*1024*1024*sizeof(char));
	if (NULL != ptr)   //必須判斷
	{
		;
	}
	else
	{
		perror("malloc");
	}
	free(ptr);      //勿忘
	ptr = NULL;    //有必要這樣做,不然ptr成了野指標;
	return 0;
}

程式碼中的1024*1024*sizeof(char)代表一兆大小的記憶體;
可以自己去測試,當想要獲取的記憶體不足以提供時,則會執行else語句,這裡的perror函式是stdio.h中的一個庫函式,它的作用是列印一個錯誤資訊;

 

這些都是動態記憶體管理的基礎必備知識,下次會引入一些深入概念以及一些經典題型!!