1. 程式人生 > >C/C++指標之--NULL指標,零指標,野指標

C/C++指標之--NULL指標,零指標,野指標

經常在聽到野指標的時候一臉懵逼,現在是得好好總結一下了。感謝fly1988happy前輩,他的blog裡面還寫了一些關於空指標的保護政策,這些point等我對虛擬空間的訪問許可權進行總結時,再來探討。

1.空指標常量
0、0L、’\0’、3 - 3、0 * 17 (它們都是“integer constant expression”)。至於系統選取哪種形式作為空指標常量使用,則是實現相關的。一般的 C 系統選擇 (void*)0 或者 0 的居多(也有個別的選擇 0L);至於 C++ 系統,由於存在嚴格的型別轉化的要求,void* 不能象 C 中那樣自由轉換為其它指標型別,所以通常選 0 作為空指標常量(C++標準推薦)。

2.空指標
空指標不指向任何實際的物件或者函式,在試圖使用一個指標之前程式碼可以首先檢查它是否為空。反過來說,任何物件或者函式的地址都不可能是空指標。其實空指標只是一種程式設計概念,就如一個容器可能有空和非空兩種基本狀態。

3.NULL指標
NULL 是一個標準規定的巨集定義,用來表示空指標常量。因此,除了上面的各種賦值方式之外,還可以用 p = NULL; 來使 p 成為一個空指標。
C++標準庫定義的NULL指標:

// Define   NULL   pointer   value 
#ifndef   NULL 
#   ifdef   __cplusplus 
#     define   NULL    0 
# else # define NULL ((void *)0) # endif #endif // NULL

NULL是一個巨集,在C++裡面被直接被定義成了整數立即數型別的0,而在沒有__cplusplus定義的前提下,就被定義成一個值是0的void *型別指標常量。

4.零指標

零值指標,是值為0的指標,可以是任何一種指標型別,可以是通用變體型別void*,也可以是char*,int*等等。

在C++裡面,任何一個概念都要以一種語言記憶體公認的形式表現出來,例如std::vector會提供一個empty()子函式來返回容器是否為空,然而對於一個基本數值型別(或者說只是一個類似整數型別的型別)我們不可能將其抽象成一個類。來提供其詳細的狀態說明,所以我們需要一個特殊值來做為這種狀態的表現。

C++標準規定,當一個指標型別的數值是0時,認為這個指標是空的。(我們在其他的標準下或許可以使用其他的特殊值來定義我們需要的NULL實現,可以是1,可以是2,是隨實現要求而定的,但是在標準C++下面我們用0來實現NULL指標)。也就是說,在C++裡面,我們可以認為0指標就是空指標

5.空指標的內部實現(空指標的指向的記憶體位置)

標準並沒有對空指標指向記憶體中的什麼地方這一個問題作出規定,也就是說用哪個具體的地址值(0x0 地址還是某一特定地址)表示空指標取決於系統的實現。我們常見的空指標一般指向 0 地址,即空指標的內部用全 0 來表示(zero null pointer,零空指標);也有一些系統用一些特殊的地址值或者特殊的方式表示空指標(nonzero null pointer,非零空指標),具體請參見C FAQ。

在實際程式設計中不需要了解在我們的系統上空指標到底是一個 zero null pointer 還是 nonzero null pointer,我們只需要瞭解一個指標是否是空指標就可以了——編譯器會自動實現其中的轉換,為我們遮蔽其中的實現細節。注意:不要把空指標的內部表示等同於整數 0 的物件表示。一般來說,他們都是不同的,一個整數0存在一個0地址,這放生的機率很小。

6. 野指標
“野指標”不是NULL指標,是指向“垃圾”記憶體的指標。

2.1 “野指標”的成因主要有兩種:

(1)指標變數沒有被初始化。任何指標變數剛被建立時不會自動成為NULL指標,它的預設值是隨機的,它會亂指一氣。所以,指標變數在建立的同時應當被初始化,要麼將指標設定為NULL,要麼讓它指向合法的記憶體。例如:

char *p = NULL;       //正確
char *str = (char *) malloc(100);  //str指向一個有100bytes位元組的記憶體空間

其實,這就是要告訴我們,使用未經初始化的指標是引發執行時錯誤的一大原因。

(2)指標p被free或者delete之後,沒有置為NULL,讓人誤以為p是個合法的指標。

free和delete只是把指標所指的記憶體給釋放掉,但並沒有把指標本身幹掉。free以後其地址仍然不變(非NULL),只是該地址對應的記憶體是垃圾,p成了“野指標”。如果此時不把p設定為NULL,會讓人誤以為p是個合法的指標。如果程式比較長,我們有時記不住p所指的記憶體是否已經被釋放,在繼續使用p之前,通常會用語句if (p != NULL)進行防錯處理。很遺憾,此時if語句起不到防錯作用,因為即便p不是NULL指標,它也不指向合法的記憶體塊。

char *p = (char *) malloc(100);
strcpy(p, “hello”);
free(p);   // p 所指的記憶體被釋放,但是p所指的地址仍然不變if(p != NULL)      // 沒有起到防錯作用
{
    strcpy(p, “world”);      // 出錯
}

(3)指標操作超越了變數的作用範圍。這種情況讓人防不勝防,示例程式如下:

class A 
{      
public:
     void Func(void){ cout << “Func of class A” << endl;                                    }
};
void Test(void)
{   
    A *p;
   {
      A a;
      p = &a; // 注意 a 的生命期,只在這個程式塊中(花括號裡面的兩行),而不是整個test函式
   }
     p->Func();  // p是“野指標”
}

函式Test在執行語句p->Func()時,物件a已經消失,而p是指向a的,所以p就成了“野指標”。