• 悬挂指针


    指针悬挂

    指针是C/C++语言中一种特殊的数据类型,它的值是一块内存区域的地址。使用指针要求,它的值必须是指向一块分配给你使用的地址,且使用的内存不能超过它分配时的大小。例如:

     char * p = new char[10];

    这样的代码就给p分配一块有10个字节的内存,并把这块内存的开始地址放在p中。用户在使用时,必须保证引用的内存必须在以p开始到这块内存结束的范围内。

    闲话少叙,说说指针悬挂。所谓指针悬挂是指指针指向了一块没有分配给用户使用的内存。指针悬挂一般由以下几种情况:

    指针未初始化

    这不仅仅是初学者才会犯的错误。尤其是全局指针变量,不初始化就使用的情况很正常,考虑如下代码:

     

     char * g_pBuffer ;

     int    g_nSize;

     void InitBuffer(int size)

     {

     

      g_nSize = size;

      g_pBuffer = new char[size];

     

     }

     

     void DumpBuffer()

     {

     

      CFile file;

      file.Open(filename,CFile::modeWrite);

      file.WriteHuge(g_pBuffer,g_nSize);

     

     }

     

    这段代码如果InitBuffer只被调用一次且在DumpBUffer之前被调用自然没有问题,问题是,对于大项目中,这种函数调用的先后关系往往是很复杂的,这样就无法保证InitBuffer被先调用,在此函数被调用之前,这个指针就是一个悬挂着的指针。良好的编程习惯是:首先给指针初始化为一个0值(注意我这里没有用空值,因为空值这个术语含义不明),然后在使用的时候检查这个指针,修改后的代码如下:

     

     char * g_pBuffer =(char*)0;

     int    g_nSize   =0;

     void InitBuffer(int size)

     {

     

      if((char*)0 != g_pBuffer)

      {

     

       delete [] g_pBuffer;

     

      }

      g_nSize = size;

      g_pBuffer = new char[size];

     

     }

     

     void DumpBuffer()

     {

     

      if((char*)0 == g_pBuffer)

      {

     

       InitBuffer(100);

     

      }

      CFile file;

      file.Open(filename,CFile::modeWrite);

      file.WriteHuge(g_pBuffer,g_nSize);

     

     }

     

    指针拷贝后删除了指针

    如果在使用指针过程中对指针进行了拷贝,然后其中一个拷贝被删除,则另外一个拷贝就成了悬挂指针,如下代码就是一个例子:

     

     char * g_pBuffer =(char*)0;

     int    g_nSize   =0;

     void InitBuffer(int size,char * pBuffer)

     {

     

      if((char*)0 != g_pBuffer)

      {

     

       delete [] g_pBuffer;

     

      }

      g_nSize = size;

      g_pBuffer = pBuffer;

     

     }

     

     void DumpBuffer()

     {

     

      if((char*)0 == g_pBuffer)

      {

     

       InitBuffer(100);

     

      }

      CFile file;

      file.Open(filename,CFile::modeWrite);

      file.WriteHuge(g_pBuffer,g_nSize);

     

     }

     

     void UseBuffer()

     {

     

      char * pBuffer = new char[100];

      InitBuffer(100,pBuffer);

      delete []pBuffer;

      DumpBuffer();

     

     }

     

     

    关注蓝色代码,在InitBuffer之后,pBuffer立刻被删除,而此时g_pBuffer还保存这个指针的一个备份,这个备份就成为一个

    悬挂的指针。因此在保存备份的时候一定要小心,否则就会出问题。

     

    类和结构中的指针悬挂

     

    在类和结构中的指针则更容易出危险。我们假设要设计一个字符串类如下:

    class CMyString

    {

    protected:

     

     int m_nSize;

     char* m_pData;

     

    public:

     

     CMyString(const char * pStr);

     virtual ~CMyString();

     

    };

    CMyString::CMyString(const char * pstr)

    {

     

     m_nSize = strlen(pstr);

     m_pData = new char[m_nSize +1];

     strcpy(m_pData,pstr);

     

    }

    CMyString::~CMyString()

    {

     

     delete[]m_pData

     

    }

     

    这段代码似乎没有问题,实际上隐含了很严重的问题。考虑下面这段代码:

    CMyString GetString()

    {

     

     

    1. 1.   CMyString str1 = "haha";
    2. 2.   
    3. 3.   CMyString str2 = "xixi";
    4. 4.   
    5. 5.   CMyString str3 = str1;
    6. 6.   
    7. 7.   str2 = str1;
    8. 8.   

     

     return str3;

     

    }

     

    上述代码中,1、2行分别初始化了一个CMyString对象,第三行则使用str1来初始化str3,第四行则使用等于号赋值。下面对于

    第三行和第四行分别说明问题。

     

    拷贝构造函数问题

     

    对于第三行,系统调用CMyString的拷贝构造函数来初始化str3,其调用格式等价于

     

    CMyString str3(str1);

     

    由于CMyString没有提供拷贝构造函数,编译器会把str1的内容原原本本的复制给str3,这样,str1.m_pData这个指针也

    被复制给str3了。在函数退出时,str1首先析构(顺序和编译器有关,这里只是一个假设,其实谁先析构问题都一样),

    其析构函数删除了m_pData,此时str3的m_pData就成为一个悬挂指针。当str3析构时,它试图删除m_pData必然造成一个异常。

     

    对于GetString函数本身而言,它返回了str3对象,返回过程会创建一个CMyString临时对象,并用str3作为参数调用拷贝构造

    函数,其结果和前面所述一样。

     

    operator=问题

     

    在第四行,程序使用=运算符给str2赋值。由于类没有提供operator =,编译器缺省实现是把str1的内容完全复制给str2,

    这样导致的后果和前面是一样的。

     

    避免指针悬挂的要点

     

     

    1. 不保存指针的拷贝,如果要保存指针指向的内容,则新分配一块大小相等的内存,把其内容完全拷贝过去。如果确实不得不
    2. 保存拷贝,必须小心关注每个拷贝的使用情况,一旦一个拷贝进行了删除,则其他拷贝必须立刻放弃使用。
    3.  
    4.  
    5.  
    6.  
    7.  
    8.  
    9.  

    10.包含指针的结构和类必须实现拷贝构造函数和operator=运算符,如果不愿意实现,对其赋值和构造必须

    11.针对每个成员变量进行特殊处理。

    13.如果结构或者类的成员变量直接或者间接的包含指针,也必须如前一条处理(例如结构中定义了string成员)

    15.指针使用完毕并删除后,应该把指针变量的值设置为0。

  • 相关阅读:
    解决ecshop进入后台服务器出现500的问题
    Java8新特性(拉姆达表达式lambda)
    使用Optional优雅处理null
    Arrays.asList 存在的坑
    Java提供的几种线程池
    冒泡排序及优化详解
    如何让MySQL语句执行加速?
    关于https的五大误区
    127.0.0.1和0.0.0.0地址的区别
    宽带网络技术-大题重点
  • 原文地址:https://www.cnblogs.com/fickleness/p/3149005.html
Copyright © 2020-2023  润新知