常見錯誤
指標的不正確使用通常是可能包含安全漏洞或程式崩潰的錯誤源,最常見的原因是分段錯誤。
不檢查分配失敗
記憶體分配不能保證成功,而是可以返回 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
取消分配記憶體會導致不可重用的記憶體累積,程式不再使用該記憶體; 這稱為記憶體洩漏 。記憶體洩漏會浪費記憶體資源,並可能導致分配失敗。
邏輯錯誤
所有分配必須遵循相同的模式:
- 使用
malloc
(或calloc
)分配 - 用於儲存資料
- 使用
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
所指向的。