C 語言程式設計教學:指標 (Pointer)

PUBLISHED ON JUL 12, 2018 — PROGRAMMING
FacebookTwitter LinkedIn LINE Skype EverNote GMail Yahoo Email

    指標 (pointer) 是 C (或 C++) 中用來管理記憶體的語法特性,指標內儲存的值是記憶體的位置 (address)。一般學 C (或 C++) 時產生的陰影一部分就是來自於指標,但指標其實並不是什麼洪水猛獸,使用一陣子就會自然而然地習慣其使用方式。

    了解 C 語言中記憶體的層次,也會對使用指標有幫助。在 C 語言中,記憶體有三個層次:

    • static:用於全域變數 (global variable)
    • stack:用於局部變數 (local variable)
    • heap:為動態記憶體 (dynamic memory)

    註:heap memory 和 heap 結構是兩種不同的東西,請勿搞混。

    前兩者會自動釋放,heap memory 則需手動管理。

    註:本文僅說明指標最基本的使用法,後續文章會說明其他指標使用的的情境。

    宣告和使用指標

    以下的例子使用到 stack memory,所以不需手動釋放記體體:

    #include <stdlib.h>
    
    int main(void)
    {
        // An automatic variable using stack memory.
        int i = 3;
        
        // Declare a pointer to int.
        int *i_p = NULL;
        
        // Point to the address of i.
        i_p = &i;
        
        return 0;
    }
    

    首先,我們宣告並指派變數 i,程式已經配置好一塊 stack memory。

    接著我們宣告一個指向整數的指標 i_p,宣告的方式是在變數旁加上星號 *;以本例來說,i_p 是一個指標,指向整數。如果宣告指標時沒有要將該指標指向某個位址,最好先將其值設為 NULL

    在本例中,我們將 i_p 的位址指向 i 所在的位址,對變數 i 前加上 & 可以取得其位址。

    如果我們用 Valgrind 檢查此程式,可發現此程式沒有從 heap 分配記憶體,也沒有造成記憶體洩露。

    像以下的例子有配置 heap memory,就需手動釋放:

    #include <stdlib.h>
    
    int main()
    {
        // Allocate a chunk of heap memory.
        int *i_p = malloc(sizeof(int));
        if (!i_p) {
            return EXIT_FAILURE;
        }
        
        // Free i_p.
        free(i_p);
        
        return 0;
    }
    

    配置記憶體的函式為 malloc,配置時需傳入記體體的大小,通常是搭配 sizeof 來取得某個型別的大小。平常練習時 malloc 大多都會成功,但其實 malloc 有可能會失敗,最好還是加入錯誤檢查的程式。在本例中,當 malloc 失敗時就中止程式,並回傳程式失敗的狀態碼。

    釋放記憶體的函式為 free,將指標傳入即可釋放記憶體。處理記憶體的函式位於 stdlib.h 函式庫中。

    mallocfree 應當成對出現,每當有用 malloc (或其他同質函式) 配置 heap memory,就要於後續的程式中搭配 free 來釋放記憶體。

    如果我們用 Valgrind 檢查此程式,可發現此程式有從 heap 配置記憶體,但已釋放了,沒有造成記憶體洩露。

    藉由指標間接修改值

    指標本身存的是位址,常見的操作是透過指標間接修改值。見下例:

    #include <assert.h>
    
    int main(void)
    {
        int n = 3;
        
        // Declare n_p, which points to n.
        int *n_p = &n;
        
        // Access n indirectly.
        assert(*n_p == 3);
        
        // Mutate n indirectly.
        *n_p = 5;
        
        // We assure that n is modified.
        assert(n == 5);
    
        return 0;
    }
    

    我們宣告一個指向整數的指標 n_p,將其指向 n,這於前文已描述過。

    我們透過 n_p 間接取得 n 的值,取值的方法是在變數前加上 * (星號)。

    接著,我們透過 n_p 間接修改 n 的值,更動值的方式是在變數前加上 * (星號後),對其進行指派的動作。

    最後,我們透過 n 本身來確認,的確 n 的值更動了。

    細心的讀者可以發現,本例沒有引入 stdlib.h 但程式可正常運作。可知指標和 heap memory 不一定要掛勾。

    小整理

    C 語言中指標相關的運算子有兩種:

    • &var
    • *var

    & 皆為取址 (address of value)。

    在宣告變數時加上 * 代表該變數為指標型別,在其他地方使用 * 則是取值 (value of address)。

    void 指標

    void 指標 (void *) 可指向任意指標型別,像是 free 函式的參數即為 void 指標。利用 void 指標可在 C 語言中模擬泛型程式的特性。