值爲NULL的對象指針

  相信你們對NULL不會很陌生,NULL 是一個標準規定的宏定義,用來表示空指針常量,當一個指針變量被賦值爲NULL時,表示它再也不指向任何有效地址,沒法在訪問任何數據。在VS2012庫文件stdio.h中有以下定義:數組

1 #ifdef __cplusplus 2 #define NULL    0
3 #else
4 #define NULL    ((void *)0)

  NULL常常被用做指針初始化行爲,以及free/delete某一指針後對其的賦值操做,這都是咱們所鼓勵的一些好的習慣操做。在對NULL指針進行解引用或者指向操做,會出現coredump或者其餘undefined行爲,而若是delete NULL指針的操做則是合法的。如今來看文中提到的值爲NULL的對象指針,以下面的例子:函數

 1 class A  2 {  3 public:  4     void foo()  5  {  6         cout<<"call foo"<<endl;  7  }  8 };  9 int _tmain(int argc, _TCHAR* argv[]) 10 { 11     A *p = NULL; 12     p->foo(); 13 
14     return 0; 15 }

   這裏使用NULL指針p,調用成員函數foo(),運行時程序輸出了「call foo",而沒有coredump或者出現別的undefined行爲,這是怎麼回事呢?咱們知道,對於類中的成員函數,隸屬於全部類的對象,程序編譯後,成員函數的地址已經肯定,而成員函數訪問類中的成員變量時,須要藉助this指針對各個對象進行區分。看到類A的成員函數foo原型爲void A::foo();,編譯器會在發生函數在調用時,將其解釋爲void A::foo(A *const this);。注意這裏的參數,this指針表示調用此函數的類A的對象地址。對於調用端,編譯器將進行以下解釋:this

1 A a; 2 a.foo();    //->foo(&a);
3 A *p = NULL; 4 p->foo();    //->foo(p);

  例子中傳入的p爲NULL,而在成員函數foo中,並未有用到經過this指針訪問成員變量的語句,因此經過NULL指針調用foo時,獲得了正確的結果。若是咱們改一下類A的定義,添加成員變量,而且在foo中進行訪問,以下:spa

 1 class A  2 {  3 public:  4     void foo()  5  {  6         cout<<"call foo"<<endl;  7         cout<<"value = "<<value<<endl;  8  }  9 private: 10     int value; 11 };

  這是仍然用p(NULL)指針來調用成員函數foo,則會使程序崩潰,由於編譯器會將foo解釋爲:指針

void A::foo(A *const this) { cout<<"call foo"<<endl; cout<<"value = "<<this->value<<endl; }

  這裏傳入的this爲NULL,函數對NULL指針進行了指向操做,產生了爲undefined行爲。code

  this指針使得類的成員函數能夠區分開各個對象之間的成員變量,那麼若是類的成員變量屬於類而不是屬於類的對象呢,即static類型的成員對象對象

  相信你們對NULL不會很陌生,NULL 是一個標準規定的宏定義,用來表示空指針常量,當一個指針變量被賦值爲NULL時,表示它再也不指向任何有效地址,沒法在訪問任何數據。在VS2012庫文件stdio.h中有以下定義:blog

1 #ifdef __cplusplus 2 #define NULL    0
3 #else
4 #define NULL    ((void *)0)

  NULL常常被用做指針初始化行爲,以及free/delete某一指針後對其的賦值操做,這都是咱們所鼓勵的一些好的習慣操做。在對NULL指針進行解引用或者指向操做,會出現coredump或者其餘undefined行爲,而若是delete NULL指針的操做則是合法的。如今來看文中提到的值爲NULL的對象指針,以下面的例子:原型

 1 class A  2 {  3 public:  4     void foo()  5  {  6         cout<<"call foo"<<endl;  7  }  8 };  9 int _tmain(int argc, _TCHAR* argv[]) 10 { 11     A *p = NULL; 12     p->foo(); 13 
14     return 0; 15 }

   這裏使用NULL指針p,調用成員函數foo(),運行時程序輸出了「call foo",而沒有coredump或者出現別的undefined行爲,這是怎麼回事呢?咱們知道,對於類中的成員函數,隸屬於全部類的對象,程序編譯後,成員函數的地址已經肯定,而成員函數訪問類中的成員變量時,須要藉助this指針對各個對象進行區分。看到類A的成員函數foo原型爲void A::foo();,編譯器會在發生函數在調用時,將其解釋爲void A::foo(A *const this);。注意這裏的參數,this指針表示調用此函數的類A的對象地址。對於調用端,編譯器將進行以下解釋:編譯器

1 A a; 2 a.foo();    //->foo(&a);
3 A *p = NULL; 4 p->foo();    //->foo(p);

  例子中傳入的p爲NULL,而在成員函數foo中,並未有用到經過this指針訪問成員變量的語句,因此經過NULL指針調用foo時,獲得了正確的結果。若是咱們改一下類A的定義,添加成員變量,而且在foo中進行訪問,以下:

 1 class A  2 {  3 public:  4     void foo()  5  {  6         cout<<"call foo"<<endl;  7         cout<<"value = "<<value<<endl;  8  }  9 private: 10     int value; 11 };

  這是仍然用p(NULL)指針來調用成員函數foo,則會使程序崩潰,由於編譯器會將foo解釋爲:

void A::foo(A *const this) { cout<<"call foo"<<endl; cout<<"value = "<<this->value<<endl; }

  這裏傳入的this爲NULL,函數對NULL指針進行了指向操做,產生了爲undefined行爲。

  對於C++中類的靜態函數,它只能訪問類的靜態成員變量,由於在編譯器對靜態函數的解釋不會包含this指針,根據這種特性,咱們知道NULL指針一樣能夠調用類中的靜態成員函數,以此訪問靜態成員變量:

 1 class A  2 {  3 public:  4     static void foo()  5  {  6         cout<<"call foo"<<endl;  7         cout<<"value = "<<value<<endl;  8  }  9 private: 10     static int value; 11 }; 12 int A::value = 0; 13 
14 int _tmain(int argc, _TCHAR* argv[]) 15 { 16     A *p = NULL; 17     p->foo(); 18     return 0; 19 }

  咱們知道類的成員函數中能夠定義普通函數、靜態函數、還能夠定義虛函數,已實現多態的效果,那麼對於類中的虛函數,用NULL指針調用時會有怎樣的後果呢,將上面的代碼簡單修改下:

 1 class A  2 {  3 public:  4     virtual void foo()  5  {  6         cout<<"call foo"<<endl;  7  }  8 private:  9     int value; 10 }; 11 int _tmain(int argc, _TCHAR* argv[]) 12 { 13     A *p = NULL; 14     p->foo(); 15 
16     return 0; 17 }

  運行的結果居然崩潰,foo函數和以前同樣,沒有訪問成員變量value,不會出現NULL指針指向的問題,但程序爲什麼崩潰了呢?緣由在於foo函數被定義爲了虛函數,這些虛函數中的地址(函數指針)保存在虛函數表中,能夠將它看作一個函數指針數組(列表),含有虛函數的類擁有一張虛函數表,而此類的每個對象在構造時,由編譯器將一個指向虛函數表的指針安插進來,成爲虛表指針,而它的做用是在咱們調用虛函數時,能更準確的訪問到虛函數表中此函數的指針,從而進行調用。回過頭來看看以前的例子,咱們根據類A的NULL指針p調用它的虛函數,而此時因爲沒有進行初始化,因此虛指針沒有安插進去,因此程序崩潰。

相關文章
相關標籤/搜索