malloc()、free()、calloc() 與 realloc()


malloc()、free()、calloc() 與 realloc()
到目前為止,都是事先宣告好所要使用的變數,當程式開始執行時,這些變數就會自動被配置記憶體空間。

然而有時有些變數並不知道何時會被使用,若希望在使用到的時候再配置空間給變數,並在變數不使用的時候,將變數所佔有的空間還給記憶體,這時候可以使用 malloc()free() 函式。

舉個簡單的例子來說,可以在程式中以動態的方式來配置一個 int 型態大小的記憶體,例如:

int *ptr = malloc(sizeof(int));

在這段程式中,malloc() 運算子會配置一個 int 需要的空間,並傳回該空間的位址,所以使用指標 ptr 來儲存這個位址,這段程式只配置空間但不初始空間中的儲存值。

以下使用一個簡單的程式來示範動態配置的使用:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *ptr = malloc(sizeof(int));

    printf("空間位置:%p\n", ptr);
    printf("空間儲存值:%d\n", *ptr);

    *ptr = 200;

    printf("空間位置:%p\n", ptr);
    printf("空間儲存值:%d\n", *ptr);

    free(ptr);

    return 0;
}

執行結果:

空間位置:0xb27010
空間儲存值:0
空間位置:0xb27010
空間儲存值:200

使用 malloc() 函式動態配置的空間,在整個程式結束前並不會自動歸還給記憶體,必須使用 free() 函式將空間還給記憶體,如上面的程式在結束前所作的動作。

在這個程式中,雖然顯示完畢後程式也就結束,但這邊還是示範了 free() 的用法,而這也是個好習慣,日後程式在持續執行過程中,若大量使用 malloc() 而沒有適當地使用 free() 的話,由於空間一直沒有歸還,最後將導致整個記憶體空間用盡。

接下來看一個簡單的動態記憶體配置的應用,陣列使用的一個缺點,就是陣列的大小必須事先決定好,然而有時候無法知道要使用多大的陣列,或者希望由使用者自行決定陣列大小,這時候就可以使用動態記憶體配置,加上指標運算來解決這個問題,先說明陣列動態配置的方式,如下所示:

int *arr = malloc(1000 * sizeof(int));

這段程式碼動態配置了 1000 個 int 大小的空間,並傳回空間的第一個位址,配置後的空間資料是未知的,可以使用 calloc() 來宣告空間配置,例如:

int *arr = calloc(1000, sizeof(int));

這個程式將宣告 1000 個 int 大小的空間,並將所有的空間值初始為 0。同樣地,使用 malloc()calloc() 配置得來的空間,在不使用時應該使用 free() 釋放,方法如下:

free(arr);

下面這個程式是個陣列動態配置的簡單示範:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int size = 0;

    printf("請輸入陣列長度:");
    scanf("%d", &size);

    int *arr = malloc(size * sizeof(int));

    printf("顯示元素值:\n");
    for(int i = 0; i < size; i++) {
        printf("arr[%d] = %d\n", i, *(arr+i));
    }

    printf("指定元素值:\n");
    for(int i = 0; i < size; i++) {
        printf("arr[%d] = ", i);
        scanf("%d" , arr + i);
    }

    printf("顯示元素值:\n");
    for(int i = 0; i < size; i++) {
        printf("arr[%d] = %d\n", i, *(arr+i));
    }

    free(arr);

    return 0;
}

執行結果:

請輸入陣列長度:4
顯示元素值:
arr[0] = 0
arr[1] = 0
arr[2] = 0
arr[3] = 0
指定元素值:
arr[0] = 1
arr[1] = 2
arr[2] = 3
arr[3] = 4
顯示元素值:
arr[0] = 1
arr[1] = 2
arr[2] = 3
arr[3] = 4

也可以使用指標來模擬二維陣列,只要清楚二維陣列中的兩個維度的索引值之位移量就可以了,這在 二維陣列 中有談到了,下面這個程式是個簡單的示範:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int m = 0;
    int n = 0;

    printf("輸入二維陣列維度(m*n):");
    scanf("%d*%d", &m, &n);

    int *ptr = malloc(m * n * sizeof(int));

    for(int i = 0; i < m; i++) {
        for(int j = 0; j < n; j++) {
            *(ptr + n*i + j) = i + j;
        }
    }

    for(int i = 0; i < m; i++) {
        for(int j = 0; j < n; j++) {
            printf("%d\t", *(ptr+n*i+j));
        }
        putchar('\n');
    }

    free(ptr);

    return 0;
}

執行結果:

輸入二維陣列維度(m*n):3*2
0   1   
1   2   
2   3   

如果要改變先前配置的記憶體大小,則可以使用 realloc(),您必須先使用 malloc()calloc()realloc() 配置記憶體,而後使用所得到的位址來使用 realloc() 重新配置記憶體大小,例如:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int size = 0;

    printf("請輸入陣列長度:");
    scanf("%d", &size);
    int *arr1 = malloc(size * sizeof(int));

    printf("指定元素值:\n");
    for(int i = 0; i < size; i++) {
        printf("arr1[%d] = ", i);
        scanf("%d" , arr1 + i);
    }

    printf("顯示元素值:\n");
    for(int i = 0; i < size; i++) {
        printf("arr1[%d] = %d\n", i, *(arr1+i));
    }

    int *arr2 = realloc(arr1, sizeof(int) * size * 2);
    printf("顯示元素值:\n");
    for(int i = 0; i < size * 2; i++) {
        printf("arr2[%d] = %d\n", i, *(arr2+i));
    }

    printf("arr1 address: %p\n", arr1);
    printf("arr2 address: %p\n", arr2);

    free(arr2);

    return 0;
}

執行結果:

請輸入陣列長度:2
指定元素值:
arr1[0] = 1
arr1[1] = 2
顯示元素值:
arr1[0] = 1
arr1[1] = 2
顯示元素值:
arr2[0] = 1
arr2[1] = 2
arr2[2] = 0
arr2[3] = 0
arr1 address: 0x10f6830
arr2 address: 0x10f6830

在這邊要注意的是,上例中,arr1arr2 的位址相同並不保證,realloc() 會需要複製資料來改變記憶體的大小,若原位址有足夠的空間,則使用原位址調整記憶體的大小,若空間不足,則重新尋找足夠的空間來進行配置,在這個情況下,realloc() 前舊位址的空間會被釋放掉,因此,必須使用 realloc() 傳回的新位址,而不該使用舊位址,若 realloc() 失敗,則傳回空指標(null)。最好是進行檢查:

int *arr2 = realloc(arr1, sizeof(int) * size * 2);
if(!arr2) {
    arr1 = arr2;
}
....
free(arr1);