• 什么是野指针?


            今天 偶然看到了C++中有关野指针的概念,就到百度和博客园查了一下,还是挺有收获的。

    野指针的定义:“野指针”不是NULL指针,是指向“垃圾”内存(不可用内存)的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是“野指针”是很危险的,if无法判断一个指针是正常指针还是“野指针”。有个良好的编程习惯是避免“野指针”的唯一方法。

     野指针形成的成因:

    一、指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。

     

      二、指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。别看free和delete的名字(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。通常会用语句if (p != NULL)进行防错处理。很遗憾,此时if语句起不到防错作用,因为即便p不是NULL指针,它也不指向合法的内存块。例:

     

      #include <stdio.h>

     

      #include <string.h>

     

      #include <malloc.h>

     

      int main(void)

     

      {

     

      int i;

     

      char *p = (char *) malloc(100);

     

      strcpy(p, "hello");

     

      free(p); // p 所指的内存被释放,但是p所指的地址仍然不变,原来的内存变为“垃圾”内存(不可用内存

     

      if(p != NULL) // 没有起到防错作用

     

      strcpy(p, "world");

     

      for(i=0;i<5;i++) //i=5后为乱码

     

      printf("%c",*(p+i));

     

      printf("\n");

     

      }

     

      另外一个要注意的问题:不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。

    三、指针操作超越了变量的作用范围。这种情况让人防不胜防,示例程序如下:

     

      class A

     

      {

     

      public:

     

      void Func(void){ cout << “Func of class A” << endl; }

     

      };

     

      class B

     

      {

     

      public:

     

      A *p;

     

      void Test(void)

     

      {

     

      A a;

     

      p = &a; // 注意 a 的生命期 ,只在这个函数Test中,而不是整个class B

     

      }

     

      void Test1()

     

      {

     

      p->Func(); // p 是“野指针”

     

      }

     

      }

     

      函数 Test1 在执行语句 p->Func()时,对象 a 已经消失,而 p 是指向 a 的,所以 p 就成了“野指针” 。(注:这个实例我在VC下运行了一下却没有问题,可以正常输出,不知道哪里出了问题)

      其实这就是C++的神奇和复杂之处。

    看下面一段代码

    #include <stdio.h> 
    class CTestClass 
    {
    public:
    CTestClass( void );
    int m_nInteger;
    void Function( void );
    };
    CTestClass::CTestClass( void ) 
    {
    m_nInteger = 0;
    }
    void CTestClass::Function( void ) 
    {
    printf( "This is a test function.\n" );
    }
    void main( void ) 
    {
    CTestClass* p = new CTestClass;
    delete p;
    p->Function();
    }

    OK,程序到此为止,诸位可以编译运行一下看看结果如何。你也许会惊异地发现:没有任何的出错信息,屏幕上竟然乖乖地出现了这么一行字符串: 
    This is a test function. 
        奇怪吗?不要急,还有更奇怪的呢,你可以把主函数中加上一句更不可理喻的: 
    ((CTestClass*)NULL)->Function(); 
        这仍然没有问题!! 
        我这还有呢,哈哈。现在你在主函数中这么写,倘说上一句不可理喻,那么以下可以叫做无法无天了: 
    int i = 888; 
    CTestClass* p2 = (CTestClass*)&i;
    p2->Function();
        你看到了什么?是的,“This is a test function.”如约而至,没有任何的错误。 
        你也许要问为什么,但是在我解答你之前,请你在主函数中加入如下代码: 
    printf( "%d, %d", sizeof( CTestClass ), sizeof( int ) ); 
        这时你就会看到真相了:输出结果是——得到的两个十进制数相等。对,由sizeof得到的CTestClass的大小其实就是它的成员m_nInteger的大小。亦即是说,对于CTestClass的一个实例化的对象(设为a)而言,只有a.m_nInteger是属于a这个对象的,而a.Function()却是属于CTestClass这个类的。所以以上看似危险的操作其实都是可行且无误的。

         现在你明白为什么我的“野指针”是安全的了,那么以下我所列出的,就是在什么情况下,我的“野指针”不安全: 
        (1)在成员函数Function中对成员变量m_nInteger进行操作; 
        (2)将成员函数Function声明为虚函数(virtual)。 
        以上的两种情况,目的就是强迫野指针使用属于自己的东西导致不安全,比如第一种情况中操作本身的m_nInteger,第二种情况中变为虚函数的Function成为了属于对象的函数(这一点可以从sizeof看出来)。 
        其实,安全的野指针在实际的程序设计中是几乎毫无用处的。我写这一篇文章,意图并不是像孔乙己一样去琢磨回字有几种写法,而是想通过这个小例子向诸位写明白C++的对象实例化本质,希望大家不但要明白what和how,更要明白why。李马二零零三年二月二十日作于自宅。 
        关于成员函数CTestClass::Function的补充说明 :
        (1)这个函数是一个普通的成员函数,它在编译器的处理下,会成为类似如下的代码: 
    void Function( const CTestClass * this ) // ① 
    {
    printf("This is a test function.\n");
    }
        那么p->Function();一句将被编译器解释为: 
    Function( p ); 
        这就是说,普通的成员函数必须经由一个对象来调用(经由this指针激活②)。那么由上例的delete之后,p指针将会指向一个无效的地址,然而p本身是一个有效的变量,因此编译能够通过。并且在编译通过之后,由于CTestClass::Function的函数体内并未对这个传入的this指针进行任何的操作,所以在这里,“野指针”便成了一个看似安全的东西。
        然而若这样改写CTestClass::Function: 
    void CTestClass::Function( void ) 
    {
    m_nInteger = 0;
    }
        那么它将会被编译器解释为: 
    void Function( const CTestClass * this ) 
    {
    this->m_nInteger = 0;
    }
        你看到了,在p->Function();的时候,系统将会尝试在传入的这个无效地址中寻找m_nInteger成员并将其赋值为0,剩下的我不用说了——非法操作出现了。

      (问题出来了,我在VC上运行时,却可以对m_Integer进行更改,不知道这是问什么)。

     
        至于virtual虚函数,如果在类定义之中将CTestClass声明为虚函数: 
    class CTestClass 
    {
    public:
    // ...
    virtual void Function( void );
    };
        那么C++在构建CTestClass类的对象模型时,将会为之分配一个虚函数表vptr(可以从sizeof看出来)。vptr是一个指针,它指向一个函数指针的数组,数组中的成员即是在CTestClass中声明的所有虚函数。在调用虚函数的时候,必须经由这个vptr,这也就是为什么虚函数较之普通成员函数要消耗一些成本的缘故。以本例而言,p->Function();一句将被编译器解释为: 
    (*p->vptr[1])( p ); // 调用vptr表中索引号为1的函数(即Function)③ 
        上面的代码已经说明了,如果p指向一个无效的地址,那么必然会有非法操作。 
    备注: 
    ①关于函数的命名,我采用了原名而没有变化。事实上编译器为了避免函数重载造成的重名情况,会对函数的名字进行处理,使之成为独一无二的名称。 
    ②将成员函数声明为static,可以使成员函数不经由this指针便可调用。
    ③vptr表中,索引号0为类的type_info。

    讨论三:

    先上代码,传说中的腾讯笔试题:
    #include 'stdafx.h'  
    #include <iostream>
    #include <string>
    using std::cout;
    using std::endl;
    class Test
    {
    public:
    Test()
    {
    a = 9;
    delete this;
    }
    ~Test()
    {
    cout<<'destructor called!'<<endl;
    }
    int a;
    };

    int _tmain(int argc, _TCHAR* argv[])
    {
    Test *mytest = new Test(); //mytest的值和this指针的值是一样一样的
    cout<<mytest->a<<endl;
    return 0;
    }
    请问运行结果如何?
        常见的回答,程序会报错,通不过编译。或者说编译通过,运行时报错,因为居然Test类的构造函数删除了this指针,相当于调用了Test类的析构函数,对象不再存在,所以访问成员变量a的时候出错。
        实际的结果是,程序可以通过编译,运行时不报错,只不过打印出a的值不是9,而是内存中一个随机垃圾值
        如果想让程序运行时出错,可以这样写main函数:
    Test mytest;
    cout<<mytest.a<<endl;
    return 0;
        这样mytest是局部对象,内存在栈上分配,delete this试图释放栈上的内存,因此会报错。
        下面的代码演示了这种情况。
    int a = 6;
    delete &a; //运行时报错
        继续上面的讨论,野指针是指在delete了一个指向动态对象的指针后,没有及时置为NULL,如果对该指针进行解除引用,就会产生垃圾值。
        一个铁的纪律,彻底杜绝野指针(这道题没办法,this不能做左值,况且即使改了this,mytest也是改不了的,不再考虑范围)delete了一个指向动态对象的指针后,及时置为NULL。相应的,对指针进行解除引用前,判断指针是否为NULL。

  • 相关阅读:
    【原创】QTP中手动添加对象
    【转载】【缺陷预防技术】流程技术预防
    【资料】HP Loadrunner 11下载地址
    使用命令行操作VSS
    sql server 按时间段查询记录的注意事项
    Asp.net应用程序文件名重名引起的bug
    使用SQL语句查询表中重复记录并删除
    backgroundpositionx的兼容性问题
    关于Asp.net Development Server
    如何查看正在使用某个端口的应该程序
  • 原文地址:https://www.cnblogs.com/fightingxu/p/2829599.html
Copyright © 2020-2023  润新知