C語言之動態記憶體管理

史善孟發表於2021-04-15

關於動態記憶體管理

關於動態記憶體管理的函式原型定義在stdlib.h標頭檔案中,Linux系統中該標頭檔案所在路徑為:/usr/include/stdlib.h中定義的與動態記憶體管理相關函式的原型為:

void *malloc(size_t size);
void *calloc(size_t numElement, size_t elementSize);
void *realloc(void *ptr, size_t size);
void free(void *ptr);

從堆中分配記憶體

  • void *malloc(size_t size)
    malloc函式通過引數指定需要分配的記憶體大小(單位:位元組),若堆中存在足夠記憶體則返回起始地址,否則返回NULL,示例程式碼如下:

    #include <stdlib.h>
    #include <stdio.h>
    
    
    int main(void) {
        int *ptr = (int *) malloc(5 * sizeof(int));
        if (!ptr) {
            printf("out of memory\n");
            return -1;
        }
        /***** do something for ptr here *****/
        free(ptr);  // 手動釋放動態分配的記憶體
        ptr = NULL;  // 堆記憶體釋放後,指標一般置為空指標,否則會造成懸空指標
        return 0;
    }
    

    從堆中開闢的記憶體使用完畢必須手動釋放歸還給堆管理器

  • void *calloc(size_t numElement, size_t elementSize)
    calloc函式通過指定元素個數和元素大小分配記憶體,若堆中存在足夠記憶體則返回起始地址,否則返回NULL,示例程式碼如下:

    #include <stdlib.h>
    #include <stdio.h>
    
    
    int main(void) {
        int *ptr = (int *) calloc(5, sizeof(int));
        if (!ptr) {
            printf("out of memory\n");
            return -1;
        }
        /***** do something for ptr here *****/
        free(ptr);
        ptr = NULL;
        return 0;
    }
    

    但和malloc不同的是calloc會對開闢的記憶體進行零初始化,即int *ptr = (int *) calloc(5, sizeof(int));與下述程式碼等價(memset函式原型定義在string.h中):

    int *ptr = (int *) malloc(5 * sizeof(int));
    if (ptr) {
        memset(ptr, 0, 5 * sizeof(int));
    }
    
  • void *realloc(void *ptr, size_t size)
    realloc函式作用為擴大或縮小已分配的堆記憶體或者NULL,該函式接受兩個引數,第一個引數指定要擴大或縮小的記憶體地址;第二個引數指定擴大或縮小後的記憶體大小,示例程式碼如下:

    #include <stdio.h>
    #include <stdlib.h>
    
    
    int main(void) {
        int *ptr = (int *) malloc(5 * sizeof(int));
        if (!ptr) {
            printf("out of memory\n");
            return -1;
        }
        int *tmp_ptr = (int *) realloc(ptr, 10 * sizeof(int));
        if (!tmp_ptr) {
            printf("out of memory\n");
            free(ptr);  // 若不再需要ptr則需釋放
            ptr = NULL;
            return -1;
        }
        ptr = tmp_ptr;
        tmp_ptr = NULL;  // 一般保證開闢的堆記憶體只有一個引用,否則容易重複釋放
        /***** do something for ptr here *****/
        free(ptr);
        ptr = NULL;
        return 0;
    }
    

    使用realloc需要注意的是當記憶體不足返回為NULL時,realloc不會將引數ptr指向的記憶體釋放,需要人為動手釋放,因此不可以寫如下程式碼:

    #include <stdlib.h>
    int main(void) {
        int *ptr = (int *) malloc(5 * sizeof(int));
        if (!ptr) {
            printf("out of memory\n");
            return -1;
        }
        ptr = (int *) realloc(ptr, 10 * sizeof(int));
        /*****此處若記憶體不足realloc返回NULL,ptr將丟失之前malloc分配的記憶體地址,造成記憶體洩漏*****/
        return 0;
    }
    

釋放記憶體

  • void free(void *ptr)
    free函式的作用是將從堆中分配的記憶體釋放歸還給堆,以便後續可以分配給其他的物件,該函式的引數為動態分配記憶體的地址。使用free函式時要注意,引數必須為malloccallocrealloc的返回值或NULL,否則將產生free(): invalid pointer執行時錯誤,示例程式碼1如下:

    #include <stdio.h>
    #include <stdlib.h>
    
    
    int main(void) {
        int a = 99;
        int *ptr = &a;
        free(ptr);  // 產生執行時錯誤,因為ptr指向棧記憶體
        ptr = NULL;
        return 0;
    }
    

    示例程式碼2如下:

    int main(void) {
        int *ptr = (int *) malloc(5 * sizeof(int));
        ptr++;
        free(ptr);  // 產生執行時錯誤,因為ptr已經不指向malloc分配記憶體的首地址
        ptr = NULL;
        return 0;
    }
    

相關文章