歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

C++中的空指針和野指針

空指針常量

一個表示0值的整數常量,叫做空指針常量。例如:0、0L、1-1(它們都是值為0的整數常量表達式)以及(void*)0、void* NULL 都是空指針常量,空指針常量可以賦值給任何指針類型,因為它是變體類型(void*)。但是我們更傾向於使用NULL表示這個空指針常量。對於其它方式(比如0)來表示空指針常量雖然不會產生任何問題,但是在根本意義上並不符合空指針常量的定義。因為空指針常量的存在意義還在強調它並不指向任何對象(後面會講細節)。

空指針

空指針不指向任何實際的對象或者函數。反過來說,任何對象或者函數的地址都不可能是空指針。 空指針是一個特殊的指針,因為這個指針不指向任何地方。這意味任何一個有效的指針如果和空指針進行相等的比較運算時,結果都是false。 在程序中,得到一個空指針最直接的方法就是運用預定義的NULL,這個值在多個頭文件中都有定義。 如果要初始化一個空指針,我們可以這樣, 
  1. int *ip = NULL; 
int *ip = NULL;
校驗一個指針是否為一個有效指針時,我們更傾向於使用這種方式
  1. if(ip != NULL) 
 if(ip != NULL)
 而不是
  1. if(ip) 
 if(ip)
為什麼有人會用if(ip)這種方式校驗一個指針非空,而且在C++中不會出現錯誤呢?而且現在很多人都會這樣寫。 原因是這樣的,
  1. // Define   NULL   pointer   value   
  2. #ifndef   NULL   
  3. #   ifdef   __cplusplus   
  4. #     define   NULL      0   
  5. #   else   
  6. #     define   NULL      ((void   *)0)   
  7. #   endif   
  8. #endif //   NULL  
// Define   NULL   pointer   value 
#ifndef   NULL 
#   ifdef   __cplusplus 
#     define   NULL      0 
#   else 
#     define   NULL      ((void   *)0) 
#   endif 
#endif //   NULL 
在現在的C/C++中定義的NULL即為0,而C++中的true為≠0,所以此時的if(ip)和if(ip != NULL)是等效的。

NULL指針

NULL是一個標准規定的宏定義,用來表示空指針常量。在C++裡面被直接定義成了整數立即數的0,而在沒有__cplusplus定義的前提下,就被定義成一個值是0的 void* 類型的指針常量

零指針

零值指針,是值為0的指針,可以是任何一種類型的指針,可以是通用變體類型 void*,也可以是 char*, int* 等等。 在C++裡面,任何一個概念都以一種語言內存公認的形式表現出來,例如std::vector會提供一個empty()子函數來返回容器是否為空,然而對於一個基本數值類型(或者說只是一個類似整數類型的類型)我們不可能將其抽象成一個類(當然除了auto_ptr等智能指針)來提供其詳細的狀態說明,所以我們需要一個特殊值來做為這種狀態的表現。 C++標准規定,當一個指針類型的數值是0時,認為這個指針是空的。(我們在其它的標准下或許可以使用其它的特殊值來定義我們需要的NULL實現,可以是1,可以是2,是隨實現要求而定的,但是在標准C++下面我們用0來實現NULL指針)

空指針指向內存的什麼地方

標准並沒有對空指針指向內存中的什麼地方這一問題作出規定,也就是說用哪個具體地址值表示空指針取決於系統實現。我們常見的空指針一般指向0地址,即空指針的內部用全0來表示(zero null pointer,零空指針);也有一些系統用一些特殊的地址值或者特殊的方式表示空指針(nonzero null pointer,非零空指針),具體參見 C FAQ。 在實現編程中不需要了解在我們的系統上空指針到底是一個zero null pointer還是 nonzero null pointer,我們只需要了解一個指針是否是空指針就可以了——編譯器會自動實現其中的轉換,為我們屏蔽其中的實現細節。注意:不要把空指針的內部實現表示等同於整數0的對象表示——如上所述,有時它們是不同的。

對空指針實現的保護政策

邏輯地址和物理地址

既然我們選擇了0作為空的概念。在非法訪問空的時候我們需要保護以及報錯。因此,編譯器和系統提供了很好的政策。 我們程序中的指針其實是windows內存段偏移後的地址,而不是實際的物理地址,所以不同的地址中的零值指針指向的同一個0地址,其實在內存中都不是物理內存的開端的0,而是分段內存的開端,這裡我們需要簡單介紹一下windows下的內存分配和管理制度: windows下,執行文件(PE文件)在被調用後,系統會分配給它一個額定大小的內存段用於映射這個程序的所有內容(就是磁盤上的內容)並且為這個段進行新的偏移計算,也就是說我們的程序中訪問的所有near指針都是在我們“自家”的段裡面的,當我們需要訪問far指針的時候,我們其實是跳出了“自家的院子”到了他人的地方,我們需要一個段偏移資質來完成新的偏移(人家家裡的偏移)所以我們的指針可能是OE02:0045就是告訴我們要訪問0E02個內存段的0045號偏移,然後windows會自動給我們找到0E02段的開始偏移,然後為我們計算真實的物理地址。 所以程序A中的零值指針和程序B中的零值指針指向的地方可能是完全不同的。

空指針賦值分區

這一分區是進程的地址空間中從0x00000000 到 0x0000FFFF 的閉區間(64K 的內存大小),這 64K 的內存是一塊保留內存,不能被程序動態內存分配器分配,不能訪問,也不能使用,保留該分區的目的是為了幫助程序員捕獲對空指針的賦值。如果進程中的線程試圖讀取或者寫入位於這一分區內的內存地址,就會引發訪問違規。

為什麼空指針訪問會出現異常

歸根結底,程序中所使用的數據都需要從物理設備上獲取,即程序中的數據需要從一個真實的物理地址中讀取或者寫入。所以當一個指針的邏輯地址可以通過計算能夠准確無誤的映射到一個正確的物理地址上時,這時候數據的訪問就是正確的,程序的執行也沒有任何問題。如果一個指針為空指針,那麼該指針所指向的邏輯地址空間位於空指針賦值分區的區間上。空指針賦值分區上的邏輯地址沒有物理存儲器與之對應,因而訪問時就會產生違規訪問的異常。

野指針

野指針不是空指針,是一個指向垃圾內存的指針。

形成原因

1.指針變量沒有被初始化。

任何指針變量被剛創建時不會被自動初始化為NULL指針,它的缺省值是隨機的。所以,指針變量在創建的同時應當被初始化,要麼將指針設置為NULL,要麼讓它指向合法的內存。例如:
  1. char* p = NULL; 
  2. char* str = (char*)malloc(1024); 
char* p = NULL;
char* str = (char*)malloc(1024);

2.指針被free或者delete之後,沒有設置為NULL,讓人誤以為這是一個合法指針。

free和delete只是把指針所指向的內存給釋放掉,但並沒有把指針本身給清理掉。這時候的指針依然指向原來的位置,只不過這個位置的內存數據已經被毀屍滅跡,此時的這個指針指向的內存就是一個垃圾內存。但是此時的指針由於並不是一個NULL指針(在沒有置為NULL的前提下),在做如下指針校驗的時候
  1. if(p != NULL) 
if(p != NULL)
會逃過校驗,此時的p不是一個NULL指針,也不指向一個合法的內存塊,造成會面程序中指針訪問的失敗。

3.指針操作超越了變量的作用范圍。

由於C/C++中指針有++操作,因而在執行該操作的時候,稍有不慎,就容易指針訪問越界,訪問了一個不該訪問的內存,結果程序崩潰 另一種情況是指針指向一個臨時變量的引用,當該變量被釋放時,此時的指針就變成了一個野指針,如下
  1. A *p; // A為一個自定義對象  
  2.     A a; 
  3.     p = &a; // 注意 a 的生命期 ,只在這個程序塊中(花括號裡面的兩行),而不是整個test函數  
  4.  } 
  5.  p->Func();  // p是“野指針” 

C++ 設計新思維》 下載見 http://www.linuxidc.com/Linux/2014-07/104850.htm

C++ Primer Plus 第6版 中文版 清晰有書簽PDF+源代碼 http://www.linuxidc.com/Linux/2014-05/101227.htm

讀C++ Primer 之構造函數陷阱 http://www.linuxidc.com/Linux/2011-08/40176.htm

讀C++ Primer 之智能指針 http://www.linuxidc.com/Linux/2011-08/40177.htm

讀C++ Primer 之句柄類 http://www.linuxidc.com/Linux/2011-08/40175.htm

將C語言梳理一下,分布在以下10個章節中:

  1. Linux-C成長之路(一):Linux下C編程概要 http://www.linuxidc.com/Linux/2014-05/101242.htm
  2. Linux-C成長之路(二):基本數據類型 http://www.linuxidc.com/Linux/2014-05/101242p2.htm
  3. Linux-C成長之路(三):基本IO函數操作 http://www.linuxidc.com/Linux/2014-05/101242p3.htm
  4. Linux-C成長之路(四):運算符 http://www.linuxidc.com/Linux/2014-05/101242p4.htm
  5. Linux-C成長之路(五):控制流 http://www.linuxidc.com/Linux/2014-05/101242p5.htm
  6. Linux-C成長之路(六):函數要義 http://www.linuxidc.com/Linux/2014-05/101242p6.htm
  7. Linux-C成長之路(七):數組與指針 http://www.linuxidc.com/Linux/2014-05/101242p7.htm
  8. Linux-C成長之路(八):存儲類,動態內存 http://www.linuxidc.com/Linux/2014-05/101242p8.htm
  9. Linux-C成長之路(九):復合數據類型 http://www.linuxidc.com/Linux/2014-05/101242p9.htm
  10. Linux-C成長之路(十):其他高級議題

Copyright © Linux教程網 All Rights Reserved