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

时间: 2024-10-07 20:12:29

值为NULL的对象指针的相关文章

RestTemplate传输值为null的属性、利用FastJson将属性中有空值null的对象转化成Json字符串

一个pojo类: import lombok.Data; @Data public class Friend { private String name; private int age; private String sex; } 初始化一个Friend对象,该对象属性为"sex"对应的值设置为null: public class FriendTest { private Friend friend = new Friend(); @Before public void init()

spring mvc 处理pojo传递对象时该对象继承父类的属性在网络接收端接收该属性值总是null,why?

//=========================== 情形一: ===============================//在网络上传递User1类对象时info属性值在网络的另一端能够接收到! public class User1 implements Serializable { public String info = null; public String userName = null; public String userPWD = null; } //=========

null?对象?异常?到底应该如何返回错误信息

这篇文章记录我的一些思考.在工作了一段时间之后. 问题的核心很简单:到底如何返回错误信息. 学生时代,见到过当时的老师的代码: 1 if (foo() == null) { 2 3 } 当然,这位老师是一位比较擅长c/c++的老程序员,所以他的代码其实使用c写的.但是意思和这段代码类似.当时,我很好奇为什么要对一个方法的返回值是不是null进行判断.现在当然很清楚了:在很多win32的API里面,是通过返回值为null来传递"函数调用失败"这一种信息的. 那么,这么做好吗? 我翻看了很

C# CLR via 对象内存中堆的存储【类型对象指针、同步块索引】

最近在看书,看到了对象在内存中的存储方式. 讲到了对象存储在内存堆中,分配的空间除了类型对象的成员所需的内存量,还有额外的成员(类型对象指针. 同步块索引 ),看到这个我就有点不懂了,不知道类型对象指针是什么,指向的什么? 从网上找也没有找到,最后往下看,书中有些描述.说下我的理解: 类型对象指针:指向类型对象存储的地址,假如有一个类型Person,它在堆中有一块区域存储它内部的字段和成员以及两个额外成员(类型对象指针. 同步块索引 ),类型对象的类型对象指针指向的是System.Type的地址

C语言学习之空指针NULL以及void指针详解

本文和大家分享的主要是c 语言 空指针NULL 以及 void 指针相关内容,一起来看看吧,希望对大家 学习c语言有所帮助. 空指针 NULL 一个指针变量可以指向计算机中的任何一块内存,不管该内存有没有被分配,也不管该内存有没有使用权限,只要把地址给它,它就可以指向,C 语言没有一种机制来保证指向的内存的正确性,程序员必须自己提高警惕. 很多初学者会在无意间对没有初始化的指针进行操作,这是非常危险的,请看下面的例子: 1. #include 2. int main () { 3. char *

对象布局已知时 C++ 对象指针的转换时地址调整

在我调试和研究 netscape 系浏览器插件开发时,注意到了这个问题.即,在对象布局已知(即对象之间具有继承关系)时,不同类型对象的指针进行转换(不管是隐式的从下向上转换,还是强制的从上到下转换)时,编译器会根据对象布局对相应的指针的值进行调整.不管是 microsoft 的编译器,还是 gcc 编译器都会做这个动作,因为这和 C++ 对象模型有关. 举一个简单的例子,如下代码: #include <stdio.h> class A { public: int x; void foo1()

刚学Android遇到的问题,findViewById值为null(新版本),老鸟欢迎你的指正

环境交代: 刚学Android,在官网下载的新版的ADT 以及新版的SDK 在新版的IDE(ADT)创建项目时如果你的最小版本(minimum required SDK)要支持4.0以下版,并且目标版本为(4.0+).那么此时IDE会为你创建一个兼容包 (appcompat_v7)如下图, 创建发短信项目后就会有如下的项目目录结构 这个时候在生成的项目主Activity不是以前的那种继承的Activity,而是继承的ActionBarActivity,我把发短信的界面创建起.界面效果如下.点此时

debug经历-------&gt;java web------&gt;为什么使用接收到的表单元素的值为null

1.问题描述: jsp页面中常常有表单元素,有时候需要在该<form>中使用文件上传标签,为了实现文件上传功能,需要将<form>元素进行二进制封装,此时如果该form中还有普通类型的表单元素,使用request.getParameter("**")是无法获取普通表单元素的值的,必须使用smartupload.getRequest().getParameter()才可以获取普通表单元素的值,并且要注意该句话一定要在smartupload.upload()之后执行

C++:向函数传递对象(对象、对象指针、对象引用)

3.5.1   使用对象作为函数参数,其方法与传递基本类型的变量相同 //例3.21 使用对象作为函数参数 #include<iostream> using namespace std; class A{ public: A(int n) { t = n; } void set_i(int n) { t = n; } int get_i() { return t; } private: int t; }; void sqrt_it(A obj2) //对象obj2作为函数sqrt_it的形参,