首页 > 代码库 > 值为NULL的对象指针

值为NULL的对象指针

  相信大家对NULL不会很陌生,NULL 是一个标准规定的宏定义,用来表示空指针常量,当一个指针变量被赋值为NULL时,表示它不再指向任何有效地址,无法在访问任何数据。在VS2012库文件stdio.h中有如下定义:

1 #ifdef __cplusplus2 #define NULL    03 #else4 #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 = http://www.mamicode.com/"<<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 = http://www.mamicode.com/"<<this->value<<endl;}

  这里传入的this为NULL,函数对NULL指针进行了指向操作,产生了为undefined行为。

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

  相信大家对NULL不会很陌生,NULL 是一个标准规定的宏定义,用来表示空指针常量,当一个指针变量被赋值为NULL时,表示它不再指向任何有效地址,无法在访问任何数据。在VS2012库文件stdio.h中有如下定义:

1 #ifdef __cplusplus2 #define NULL    03 #else4 #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 = http://www.mamicode.com/"<<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 = http://www.mamicode.com/"<<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 = http://www.mamicode.com/"<<value<<endl; 8     } 9 private:10     static int value;11 };12 int A::value = http://www.mamicode.com/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的对象指针