1. 程式人生 > 其它 >深入理解動態記憶體管理

深入理解動態記憶體管理

技術標籤:C語言

1、為什麼存在動態記憶體分配?
我們之前為資料開闢的空間都是在棧上開闢的,但是,在棧上開闢空間有幾個缺點
第一個缺點:開闢空間小,沒辦法使用大空間,當開闢大空間之後,系統會發生錯誤。
第二個缺點:開闢空間是固定的,然而有的時候我們需要的空間在程式執行的時候才能知道。
基於以上兩個缺點,就出現了動態記憶體開闢的解決方法。
2、動態記憶體函式的介紹
(1)malloc和free
a、malloc
動態記憶體開闢函式:

void * malloc(size_t size);

這個函式向記憶體申請一塊連續可用的空間,並返回指向這塊空間的指標。
●如果開闢成功,則返回一個指向開闢好空間的指標。

●如果開闢失敗,則返回一個NULL指標,因此malloc的返回值一定要做檢查。
●返回值的型別是void*,所以malloc函式並不知道開闢空間的型別,具體在使用的時候使用者自己來確定。
●如果size為0,malloc的行為是標準是未定義的,取決於編譯器。
b、free
free函式是專門用來做動態記憶體的釋放和回收的,函式原型如下:

void free(void * ptr);

free函式用來釋放動態開闢的記憶體。
●如果引數ptr指向的空間不是動態開闢的,那free函式的行為就是未定義的。
●如果引數ptr是NULL指標,則函式什麼事都不做。
●free的時候,是將開闢空間內的資料變為隨機值,並不是變為0。

注意:(1)malloc申請的空間在堆上申請。
(2)因為malloc是函式,所以堆空間只能在執行時確定。
(3)malloc在堆上申請空間,程式設計師申請,程式設計師釋放。如果不釋放,會有記憶體洩漏問題。程式退出,記憶體 洩漏問題隨之消失。
(4)free時釋放的是指標和記憶體塊(堆)的關係。
(5)malloc的時候,並不是申請多少位元組就開闢多少位元組,開闢的位元組是比程式設計師申請的位元組要多的。多餘的位元組裡面放的是元資訊,比如開闢的時間、什麼人開闢的等等一系列的資訊。所以用malloc開闢空間的時候,開闢的越多越合適,畢竟無論你要開闢多少位元組,都必須開闢一段位元組用來儲存元資訊。free的時候釋放的位元組是全部空間,包括元資訊所佔的空間。

我們來舉一個例子瞭解一下malloc和free的使用:

#include<stdio.h>
#include<stdlib.h>
int main(){
int num=0;
printf("請輸入要開闢的空間大小:")
scanf("%d",&num);
char * ptr=NULL;
ptr=(char * )malloc(num*sizeof(char));
if(NULL!=ptr)//判斷ptr指標是否為空
{
int i=0;
for(i=0;i<num;i++)
    {
       *(ptr+i)=0;
    }
}
free(ptr);//釋放ptr所指向的動態記憶體
ptr=NULL;//此時,ptr指向的是隨機值,ptr就變成了野指標。這一步很有必要。
return 0;
}

2、calloc
calloc也是用來進行動態記憶體分配的,與malloc的區別是calloc會初始化。
原型如下:

void * calloc(size_t num,size_t size);

●函式的功能是為num個大小為size的元素開闢一塊空間,並把空間的每個位元組初始化為0。
●calloc也需要free釋放。
3、realloc
在進行malloc動態記憶體開闢的時候,可能會發現開闢的空間多了或者是少了,那這個時候就需要用到realloc函數了。realloc函式就可以做到對動態開闢記憶體大小的調整。
函式原型如下:

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

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

調整記憶體大小時分為兩種,一種是調大,一種是調小。調小直接在開闢的記憶體上free即可,調大不行。
調大有兩種情況:
情況1:原有空間之後有足夠大的空間,直接向後擴容即可。
在這裡插入圖片描述

情況2:原有空間之後沒有足夠大的空間,則需要重新開闢空間。
在這裡插入圖片描述在情況2的時候,就要注意一些問題,舉個例子:

#include <stdio.h>
#include<stdlib.h>
int main()
{
    int *ptr = malloc(100);     
    if(ptr != NULL)
    {
        //業務處理
    }
    else
    {
       exit(EXIT_FAILURE);
    }
    //擴充套件容量
    //程式碼1
    ptr = realloc(ptr, 1000);//這樣可以嗎?(如果申請失敗會如何?)
    //程式碼2
    int*p = NULL;
    p = realloc(ptr, 1000);     
    if(p != NULL)
    {
       ptr = p;
    }
    //業務處理
    free(ptr);
    return 0;
}

明顯,程式碼1就不嚴謹,如果是情況2的話,開闢失敗,依舊將開闢失敗的地址賦給ptr,那麼就沒辦法指向原來的資料了,得不償失。程式碼2就是正確的,既保證了開闢失敗之後,原始資料沒有受影響,並且對開闢成功與否做了判斷。