C++程序員必經之路——懸垂指針與野指針

參看維基百科:

Dangling pointer

迷途指針

===============================================================================================

溫馨提示:由於本人英語水品有限,有些地方翻譯得可能不準確,也比較生澀。

===============================================================================================

當所指向的對象被釋放或者收回,但是對該指針沒有作任何的修改,以至於該指針仍舊指向已經回收的內存地址,此情況下該指針便稱懸垂指針(也叫迷途指針)。

某些編程語言允許未初始化的指針的存在,而這類指針即爲野指針

懸垂指針的成因:

在許多編程語言中(比如C),顯示地從內存中刪除一個對象或者返回時通過銷燬棧幀,並不會改變相關的指針的值。該指針仍舊指向內存中相同的位置,即使引用已經被刪除,現在可能已經挪作他用。

一個簡單的例子:

複製代碼
{
   char *dp = NULL;
   /* ... */
   {
       char c;
       dp = &c;
   } /* c falls out of scope */
     /* dp is now a dangling pointer */
}
複製代碼

如果操作系統能夠偵測運行時的指向空指針的引用,一個方案是在內部快消失之前給dp賦爲0(NULL)。另一個方案是保證dp在沒有被初始化之前不再被使用。

另一個常見原因是混用 malloc() 和 free():當一個指針指向的內存被釋放後就會變成懸垂指針。正如上個例子,可以避免這個問題的一種方法是在釋放它的引用後把指針重置爲NULL。

複製代碼
#include <stdlib.h>
 
void func()
{
    char *dp = malloc(A_CONST);
    /* ... */
    free(dp);         /* dp now becomes a dangling pointer */
    dp = NULL;        /* dp is no longer dangling */
    /* ... */
}
複製代碼

一個很常見的失誤是返回一個棧分配的局部變量:一旦調用的函數返回了,分配給這些變量的空間被回收,此時它們擁有的是“垃圾值”。

int *func(void)
{
    int num = 1234;
    /* ... */
    return &num;
}

調用 func 後,嘗試從該指針暫時能讀取到正確的值(1234),但是再次調用函數後將會重寫棧爲 num 分配的的值,再從該指針讀取的值就不正確了。如果必須要返回一個指向 num 的指針,num 的作用域必須大於這個函數——它也許被聲明爲 static。

野指針的成因:

野指針的產生是由於在首次使用之前沒有進行必要的初始化。因此,嚴格地說,在編程語言中的所有爲初始化的指針都是野指針。

複製代碼
int f(int i)
{
    char *dp;    /* dp is a wild pointer */
    static char *scp;  /* scp is not a wild pointer:
                        * static variables are initialized to 0
                        * at start and retain their values from
                        * the last call afterwards.
                        * Using this feature may be considered bad
                        * style if not commented */
}
複製代碼

 dp 是一個野指針。scp 不是一個野指針:靜態變量一開始被初始化爲0,從最後一次調用後保持着它們的值。如果沒有註釋,使用這個特性也許被視爲不良風格。

避免懸垂指針錯誤:

在 C/C++ 中,一種最簡單的技術是實現一個 free()(或類似的)替代版本或者 delete 析構器來保證指針的重置。然後,這個技術不會清除其他指針變量,它們含有該指針的副本。

複製代碼
/* Alternative version for 'free()' */
void safefree(void **pp)
{
    if (pp != NULL) {               /* safety check */
        free(*pp);                  /* deallocate chunk, note that free(NULL) is valid */
        *pp = NULL;                 /* reset original pointer */
    }
}
 
int f(int i)
{
    char *p = NULL, *p2;
    p = (char *)malloc(1000);    /* get a chunk */
    p2 = p;              /* copy the pointer */
    /* use the chunk here */
    safefree(&p);       /* safety freeing; does not affect p2 variable */
    safefree(&p);       /* this second call won't fail */
    char c = *p2;       /* p2 is still a dangling pointer, so this is undefined behavior. */
}
複製代碼

替換版本可以用來保證在調用 malloc() 之前一個空指針的正確性:

safefree(&p);        /* i'm not sure if chunk has been released */
p = malloc(1000);    /* allocate now */

這些用法可以通過 #define 指令來構造有用的宏指令,創建像元語言的東西來掩飾或者被嵌入到一個工具庫中。但凡使用這個技術的程序員在會用到 free()的地方應該使用安全版本;不這麼做會再次導致這些問題。另外,這個解決方案侷限於單個程序或工程的作用域中,並且應該正確地寫入文檔。

在更多結構化的解決方案中,一種流行的避免懸垂指針的技術是使用智能指針。一個智能指針通常使用引用技術來收回對象。還有些技術包括 tombstones 方法和 locks-and-keys 方法。

另一個方法是使用 Boehm 垃圾收集器,一種保守的垃圾收集器,取代C和C++中的標準內存分配函數。此法通過禁止內存釋放函數來完全消除懸垂指針引發的錯誤,通過收集垃圾來回收對象。

像Java語言,懸垂指針這樣的錯誤是不會發生的,因爲Java中沒有明確地重新分配內存的機制。而且垃圾回收器只會在對象的引用數爲0時重新分配內存。


/************************************************************************** 
                  原文來自博客園——Submarinex的博客: www.cnblogs.com/submarine/                
  *************************************************************************/ 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章