值为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的对象地址。对于调用端,编译器将进行如下解释:

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行为。

  this指针使得类的成员函数可以区分开各个对象之间的成员变量,那么如果类的成员变量属于类而不是属于类的对象呢,即static类型的成员对象

  相信大家对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的对象地址。对于调用端,编译器将进行如下解释:

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调用它的虚函数,而此时由于没有进行初始化,所以虚指针没有安插进去,所以程序崩溃。

你可能感兴趣的:(null)