常見錯誤

指標的不正確使用通常是可能包含安全漏洞或程式崩潰的錯誤源,最常見的原因是分段錯誤。

不檢查分配失敗

記憶體分配不能保證成功,而是可以返回 NULL 指標。使用返回的值,而不檢查分配是否成功,呼叫未定義的行為 。這通常會導致崩潰,但不能保證會發生崩潰,因此依賴它也會導致問題。

例如,不安全的方式:

struct SomeStruct *s = malloc(sizeof *s);
s->someValue = 0; /* UNSAFE, because s might be a null pointer */

安全方式:

struct SomeStruct *s = malloc(sizeof *s);
if (s)
{
    s->someValue = 0; /* This is safe, we have checked that s is valid */
}

在請求記憶體時使用文字數字而不是 sizeof

對於給定的編譯器/機器配置,型別具有已知大小; 但是,沒有任何標準可以定義給定型別(char 除外)的大小對於所有編譯器/機器配置都是相同的。如果程式碼使用 4 代替 sizeof(int) 進行記憶體分配,它可能在原始機器上執行,但程式碼不一定可移植到其他機器或編譯器。型別的固定尺寸應替換為 sizeof(that_type)sizeof(*var_ptr_to_that_type)

非行動式分配:

 int *intPtr = malloc(4*1000);    /* allocating storage for 1000 int */
 long *longPtr = malloc(8*1000);  /* allocating storage for 1000 long */

行動式分配:

 int *intPtr = malloc(sizeof(int)*1000);     /* allocating storage for 1000 int */
 long *longPtr = malloc(sizeof(long)*1000);  /* allocating storage for 1000 long */

或者,更好的是:

 int *intPtr = malloc(sizeof(*intPtr)*1000);     /* allocating storage for 1000 int */
 long *longPtr = malloc(sizeof(*longPtr)*1000);  /* allocating storage for 1000 long */

記憶體洩漏

未能使用 free 取消分配記憶體會導致不可重用的記憶體累積,程式不再使用該記憶體; 這稱為記憶體洩漏 。記憶體洩漏會浪費記憶體資源,並可能導致分配失敗。

邏輯錯誤

所有分配必須遵循相同的模式:

  1. 使用 malloc(或 calloc)分配
  2. 用於儲存資料
  3. 使用 free 進行解除分配

不遵守這種模式,例如在呼叫 free懸空指標 )之後或呼叫 malloc野指標 ) 之前使用記憶體,呼叫 free 兩次(雙重釋放)等,通常會導致分段錯誤,導致程式崩潰。

這些錯誤可能是暫時的並且難以除錯 - 例如,釋放的記憶體通常不會被作業系統立即回收,因此懸掛指標可能會持續一段時間並且似乎可以正常工作。

在它執行的系統上, Valgrind 是一個非常有用的工具,用於識別洩漏的記憶體以及最初分配的位置。

建立指向堆疊變數的指標

建立指標不會延長指向的變數的生命週期。例如:

int* myFunction() 
{
    int x = 10;
    return &x;
}

這裡,x 具有自動儲存持續時間 (通常稱為堆疊分配)。因為它是在堆疊上分配的,所以只要 myFunction 正在執行它的生命週期; 在 myFunction 退出後,變數 x 被破壞。此函式獲取 x 的地址(使用 &x),並將其返回給呼叫者,使呼叫者留下指向不存在的變數的指標。然後,嘗試訪問此變數將呼叫未定義的行為

函式退出後,大多數編譯器實際上並不清除堆疊幀,因此取消引用返回的指標通常會為你提供預期的資料。但是,當呼叫另一個函式時,指向的記憶體可能會被覆蓋,並且看起來指向的資料已被破壞。

要解決這個問題,要麼返回要返回的變數的儲存,要麼返回指向新建立的儲存的指標,要麼將有效指標傳遞給函式而不是返回一個,例如:

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

int *solution1(void) 
{
    int *x = malloc(sizeof *x);
    if (x == NULL) 
    {
        /* Something went wrong */
        return NULL;
    }

    *x = 10;

    return x;
}

void solution2(int *x) 
{
    /* NB: calling this function with an invalid or null pointer 
       causes undefined behaviour. */

    *x = 10;
}

int main(void) 
{
    { 
        /* Use solution1() */

        int *foo = solution1();  
        if (foo == NULL)
        {
            /* Something went wrong */
            return 1;
        }

        printf("The value set by solution1() is %i\n", *foo);
        /* Will output: "The value set by solution1() is 10" */

        free(foo);    /* Tidy up */
    }

    {
        /* Use solution2() */

        int bar;
        solution2(&bar); 

        printf("The value set by solution2() is %i\n", bar);
        /* Will output: "The value set by solution2() is 10" */
    }

    return 0;
}

遞增/遞減和解除引用

如果你寫*p++來增加 p 指出的東西,那你錯了。

在解除引用之前執行後遞增/遞減。因此,這個表示式將遞增指標 p 本身並返回 p 指向的內容,然後在不改變它的情況下遞增。

你應該寫 (*p)++來增加 p 指向的東西。

這個規則也適用於後遞減:*p-- 將遞減指標 p 本身,而不是 p 所指向的。