【C】一篇文章搞懂c語言動態內存管理

動態內存管理

一、爲什麼要有動態內存分配

我們已經掌握的開闢內存方式:
int a = 20;
char c = ‘c’;
int a[20] ;等等等…
以上的這些開闢的內存都是已經固定好了的,數組在聲明時,必須指定數組長度,編譯之後便不能增減了,那麼當我們遇到像只有在運行時才能知道我們需要多大的內存空間時的需求該怎麼辦呢?
答:使用動態內存函數,運行時動態申請空間,需要多少申請多少。

二、動態分配內存分配在內存的哪一塊?

在這裏插入圖片描述

三、什麼是動態內存函數

1.malloc和free

malloc

C語言提供了一個動態內存開闢的函數:

void* malloc (size_t size);
官方說明:分配大小爲字節的內存塊,返回指向塊開頭的指針
新分配的內存塊的內容未初始化,仍保留不確定的值。
如果size爲零,則返回值取決於特定的庫實現(它可能是也可能不是空指針),但返回的指針不應被取消引用。

  • 這個函數向內存中堆空間申請一塊連續可用的空間,並返回指向這塊內存空間的指針
  • 1.如果開闢成功,則返回一個指向開闢好空間的指針
  • 2.如果開闢失敗,則返回一個NULL指針,因此要對malloc 的返回值做檢查
  • 3.返回值爲void*類型,所以需要寫代碼的人自己對該指針進行強制轉換成需要的指針類型
  • 4.如果參數size爲0,malloc會做什麼是未知的,取決於編譯器

例如:動態申請一塊2個int型的堆內存空間

int *ptr = NULL;
int num = 2;
ptr = (int*)malloc( num * sizeof(int) );
if( NULL == ptr){
	printf("申請失敗!\n");
}

變量說明:
由於需要的是int型空間,所以指針必須是int類型,故ptr是int型並且malloc返回值必須強轉爲int*型
由於需要一塊2個int型大小的空間,所以malloc需要申請空間大小爲2 * sizeof(int)字節,即8字節

由以上代碼便動態申請了一塊2個int型大小的空間

free

C語言提供了一個函數free,用來對動態開闢的內存進行釋放回收

void free (void* ptr);
官方說明:
作用:釋放內存塊
如果ptr沒有指向分配給上述函數的內存塊,則會導致未定義的行爲。
如果ptr是空指針,則函數不執行任何操作。
請注意,此函數不會更改ptr本身的值,因此它仍然指向相同(現在無效)的位置。

free(ptr);

使用free()函數便將之前申請的動態內存釋放了

注意:
在如下代碼中,ptr存儲的地址爲0X00B59B98,該片地址中存儲的是1111111十進制數字,在free之後,ptr存儲的地址仍然是爲0X00B59B98,但是該片地址中存儲的是隨機數字

在這裏插入圖片描述

2.calloc

void* calloc (size_t num, size_t size);
官方說明:分配一塊num個大小爲size字節的內存塊,並將每一初始化爲0
size爲個數
num爲每個的字節數
總大小 = num * size

  • calloc函數與malloc函數的區別在於:calloc會將動態分配的內存中的數據初始化爲0,而malloc動態申請的內存中的數據是隨機值

在這裏插入圖片描述

3.realloc

想一想:有時我們動態分配的內存大小,萬一出現了偏大或偏小了,爲了最大限度的利用內存,我們應該怎麼辦呢?
答案:C語言提供了一個函數,可以修改已經動態分配的內存大小

void* realloc (void* ptr, size_t size);
官方說明
作用:重新分配內存塊,更改ptr指向的內存塊的大小。
size變量說明:內存塊的新大小,以字節爲單位;size_t是無符號整數類型。
函數可以將內存塊移動到新的位置(其地址由函數返回)。
內存塊的內容將保留到新大小和舊大小中的較小值,即使塊被移動到新位置。如果新大小更大,則新分配部分的值不確定。
如果ptr是空指針,那麼函數的行爲類似malloc,分配一個新的大小字節塊,並返回一個指向其開頭的指針。

  • realloc函數讓動態內存管理更加靈活
  • ptr是要調整的內存地址
  • size是調整之後的新大小
  • 返回值爲調整之後的內存起始位置
  • 這個函數調整原內存空間大小,同時可能會將這片內存中的數據移動到新的地方。(原因是對原內存空間增大之後,增加的連續內存是被別的程序使用的內存,這樣就無法增加,所以需要重新找到一塊連續的且足夠的內存空間)

realloc一般在內存中有兩種情況:
情況一
在這裏插入圖片描述在這裏插入圖片描述

情況二:
在這裏插入圖片描述在這裏插入圖片描述

四、常見的動態內存錯誤

1.內存泄漏

void test()
{
	int *p = (int *)malloc(100);	//動態分配內存
	if(NULL != p)
	{
		*p = 20;
	}
	//未釋放內存,導致內存泄漏
}
int main()
{
	test();
	while(1);
}

2.同塊內存多次釋放

void test()
{
	int *p = (int *)malloc(100);
	free(p);
	free(p);	//重複釋放
}

3.內存未全部釋放

void test(){
	int *p = (int *)malloc(100);
	p++;	//p指針已經不是指向動態開闢的內存的起始位置了
	free(p);	//p指向空間內存只釋放了一部分
}

4.釋放非動態內存

int a = 10;
int *p = &a;
free(p);	//由於p指向的空間並不是動態申請的,所以無需釋放

5.越界訪問動態內存

void test()
{
	int i = 0;
	int *p = (int *)malloc(10*sizeof(int));
	if(NULL == p)
		exit(EXIT_FAILURE);
	for(i=0; i<=10; i++)	
	//一共訪問了11次,動態申請的內存只可以訪問10從
		*(p+i) = i;	//當i是10的時候越界訪問
	free(p);
}

6.解引用NULL指針

void test()
{
	int *p = (int *)malloc(INT_MAX/4);
	*p = 20;//如果p的值是NULL,就會有問題
	free(p);
}

五、給結構體動態分配空間

由於結構體是一種自定義類型,它和int、char、double、float等C語言自帶的類型是沒區別的,我們來看一看結構體如何分配內存空間:

struct Array{
	int num;
	int* mem;	//指向一個邊長空間
};
int main(){
	//由外而內申請內存
	struct Array* pArray = (struct Array*)malloc(sizeof(struct Array));
	if(pArray == NULL){	//檢查結構體申請空間是否成功
		return 1;
	}
	
	pArray->num = 10;
	
	pArray->mem = (int*)malloc(pArray->num * sizeof(int));
	if(pArray->mem == NULL){  //檢查結構體成員空間是否申請成功
		free(pArray);	//失敗返回前不要忘記釋放結構體的空間
		return 2;
	}

	//由內而外釋放內存
	free(pArray->mem);
	free(pArray);
	return 0;
}

在這裏插入圖片描述

六、柔性數組

也許你從來沒有聽說過柔性數組(flexible array)這個概念,但是它確實是存在的。
C99 中,結構中的最後一個元素允許是未知大小的數組,這就叫做『柔性數組』成員。

寫法1typedef struct A
{
	int i;
	int a[0];//柔性數組成員
};
寫法2typedef struct B
{
	int i;
	int a[];//柔性數組成員
};

柔性數組的特點:

  • 結構中的柔性數組成員前面至少一個其他成員
  • sizeof 返回的這種結構大小不包括柔性數組的內存
  • 包含柔性數組成員的結構用malloc ()函數進行內存的動態分配,並且分配的內存應該大於結構的大小,以適應柔性數組的預期大小。

例如:
在這裏插入圖片描述

上述代碼實現了一個變長數組,存放了0-9這10個數字,每一個數字存放在一個int型大小的內存中。
sizeof(struct A) + 10 * sizeof(int)中申請了一個結構體大小的內存空間外加10個int類型的內存空間,這10個int類型的內存空間就被保存在柔性數組中。
a[0]是結構體的終止地址,同時又是後面內存的起始地址。

struct A* pA = (struct A*)malloc(sizeof(struct A) + 10 * sizeof(int));

七、經典面試題

面試題一

void GetMemory(char *p){
	p = (char*)malloc(100);
}
int main(){
	char* str = NULL;
	GetMemory(str);
	strcpy(str, "hello world!");
	printf(str);
	
	return 0;
}

上述代碼有哪些錯誤?
1.p指向的動態內存未被釋放
2.試圖向空指針str中寫入數據
3.試圖輸出空指針中的內容

在這裏插入圖片描述

如何修改代碼能完美解決上述錯誤呢?
提示:使用二級指針,將str的地址傳入!

修改後代碼:

void GetMemory(char **p){
	*p = (char**)malloc(100);
}

int main(){
	char* str = NULL;
	GetMemory(&str);
	strcpy(str, "hello world!\n");
	printf(str);

	return 0;
}

輸出:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章