• C迷途指针


    简介:

    在计算机编程领域中迷途指针,或称悬空指针野指针,指的是不指向任何合法的对象的指针。

    当所指向的对象被释放或者收回,但是对该指针没有作任何的修改,以至于该指针仍旧指向已经回收的内存地址,此情况下该指针便称迷途指针。若操作系统将这部分已经释放的内存重新分配给另外一个进程,而原来的程序重新引用现在的迷途指针,则将产生无法预料的后果。因为此时迷途指针所指向的内存现在包含的已经完全是不同的数据。通常来说,若原来的程序继续往迷途指针所指向的内存地址写入数据,这些和原来程序不相关的数据将被损坏,进而导致不可预料的程序错误。这种类型的程序错误,不容易找到问题的原因,通常会导致段错误(Linux系统中)和一般保护错误(Windows系统中)。如果操作系统的内存分配器将已经被覆盖的数据区域再分配,就可能会影响系统的稳定性。某些编程语言允许未初始化的指针的存在,而这类指针即为野指针。野指针所导致的错误和迷途指针非常相似,但野指针的问题更容易被发现。

    迷途指针的成因

    野指针主要是因为这些疏忽而出现的删除或申请访问受限内存区域的指针。有以下三种情况:

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

    b.指针释放之后未置空:有时指针在free或delete后未赋值 NULL,便会使人以为是合法的。别看free和delete的名字(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。此时指针指向的就是“垃圾”内存。释放后的指针应立即将指针置为NULL,防止产生“野指针”。

    c.指针操作超越变量作用域:不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。

    在很多编程语言中(如C语言)从内存中删除一个对象或者返回时删除栈帧后,并不会改变相关的指针的值。该指针仍然指向原来的内存地址,即使引用已经删除,现在也可能已经被其它进程使用了。

    一个直接的例子,如下所示:

    {
       char *cp = NULL;
       /* ... */
       {
           char c;
           cp = &c;
       } /* c falls out of scope */          
         /* cp is now a dangling pointer */
    }

    上述问题的解决方法是在该部分程序退出之前立即给CP赋0值(NULL)。另一个办法是保证CP在没有初始化之前,将不再被使用。

    迷途指针经常出现在混杂使用malloc() 和 free() 库调用: 当指针指向的内存释放了,这时该指针就是迷途的。和前面的例子一样,一个避免这个错误的方法是在释放它的引用后将该指针的值重置为NULL,如下所示:

    #include <stdlib.h>
    {
        char *cp = malloc ( A_CONST );
        /* ... */
        free ( cp );      /* cp 现在变成了一个悬空指针 */
        cp = NULL;        /* cp 现在不是悬空了 */
        /* ... */
    }

    有个常见的错误是当返回一个基于栈分配的局部变量的地址时,一旦调用的函数返回,分配给这些变量的空间将被回收,此时它们拥有的是"垃圾值"。

    int * func ( void )
    {
        int num = 123;
        /* ... */
        return &num;
    }

    在调用func之后一段时间,尝试从该指针中读取num的值,可能仍然能够返回正确的值(123),但是任何接下来的函数调用会覆盖原来的栈为num分配的空间。这时,再从该指针读取num的值就不正确了。如果要使一个指向num的指针都返回正确的num值,则需要将该变量声明为static

    野指针的产生

    野指针指的是还没有初始化的指针。严格地说,编程语言中每个指针在初始化前都是野指针。

    一般于未初始化时便使用指针就会产生问题。大多数的编译器都能检测到这一问题并警告用户。

    int f(int i)
    {
        char* cp;    //cp 是野指针
        static char* scp;  //scp 不是野指针,静态变量自动初始化为0并保留它们的值
    //使用这种特征可能被认为坏的编程风格
    }

    规避野指针

    a.初始化时置 NULL

    指针变量一定要 初始化为NULL,因为任何指针变量(除了static修饰的指针变量)刚被创建时不会自动成为NULL指针,它的 缺省值是随机的。
    b。释放时置 NULL
    当指针p指向的内存空间释放时,没有设置指针p的值为NULL。delete和free只是把内存空间释放了,但是并没有将指针p的值赋为NULL。通常判断一个指针是否合法,都是使用if语句测试该指针是否为NULL。例如:
    1.  
      int *p=newint(6);
    2.  
      delete p;
    3.  
      // 应加入 p=NULL; 以防出错
    4.  
      // ...
    5.  
      if(p != NULL)
    6.  
      {
    7.  
        *p=7;
    8.  
        cout << p << endl;
    9.  
      }
    对于使用 free 的情况,常常定义一个宏或者函数 xfree 来代替 free 置空指针:
    1.  
      #define xfree(x) free(x); x = NULL;
    2.  
      // 在 C++ 中应使用 nullptr 指代空指针
    3.  
      // 一些平台上的 C/C++ 已经预先添加了 xfree 拓展,如 GNU 的  
    4.  
      libiberty
    5.  
      xfree(p);
    6.  
      // 用函数实现,例如 GitHub 上的 AOSC-Dev/Anthon-Starter #9:
    7.  
      static inline void *Xfree(void *ptr) {
    8.  
          free(ptr);
    9.  
      #ifdef __cplusplus
    10.  
          return nullptr;
    11.  
      #else
    12.  
          return NULL;
    13.  
      #endif
    14.  
      }
    15.  
      q=Xfree(q);

    所以动态分配内存后,如果使用完这个动态分配的内存空间后,必须习惯性地使用delete操作符取释放它。

    迷途指针导致的安全漏洞

    如同缓存溢出错误,迷途指针/野指针这类错误经常会导致安全漏洞。 例如,如果一个指针用来调用一个虚函数,由于vtable指针被覆盖了,因此可能会访问一个不同的地址(指向被利用的代码)。或者,如果该指针用来写入内存,其它的数据结构就有可能损坏了。一旦该指针成为迷途指针,即使这段内存是只读的,仍然会导致信息的泄露(如果感兴趣的数据放在下一个数据结构里面,恰好分配在这段内存之中)或者访问权限的增加(如果现在不可使用的内存恰恰被用来安全检测).

    避免迷途指针的错误

    避免迷途指针,有一种受欢迎的方法——即使用智能指针Smart pointer)。智能指针使用引用计数来回收对象。一些其它的技术包括tombstone法和locks-and-keys法

    另外,可以使用 DieHard 内存分配器,它虚拟消除了类似其它内存错误(不合法或者两次释放内存)的迷途指针错误。

    还有一种办法是贝姆垃圾收集器,一种保守的垃圾回收方法,能够替代C和C++中标准内存分配函数。这种方法完全消除了迷途指针的错误,通过去除内存释放的函数代之以垃圾回收器完成对象的回收。像Java语言,迷途指针这样的错误是不会发生的,因为Java中没有明确地重新分配内存的机制。而且垃圾回收器只会在对象的引用数为零时重新分配内存。

    迷途指针的检测

    为了能发现迷途指针,一种普遍的编程技术——一旦指针指向的内存空间被释放,就立即把该指针置为空指针或者为一个非法的地址。当空指针被重新引用时,此时程序将会立即停止,这将避免数据损坏或者某些无法预料的后果。这将使接下来的编程过程产生的错误变得容易发现和解决了。这种技术在该指针有多个复制时就无法起到应有的作用了。

    一些调试器会自动地用特定的模式来覆盖已经释放的数据,如0xDEADBEEF (Microsoft's Visual C/C++ 调试器,例如,根据哪种类型被释放采用 0xCC0xCD 或者 0xDD)。这种方法通过将数据无用化,来防止已经释放的数据重新被使用。这种方法的作用是非常显著的 (该模式可以帮助程序来区分哪些内存是刚刚释放的)。

    某些工具,如Valgrind, Mudflap或者 LLVM可以用来检测迷途指针的使用。

    作者:Chen洋

    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.

    参考:http://www.cnblogs.com/wuyudong/

     https://baike.so.com

     https://blog.csdn.net/bqxdrs012/article

     
  • 相关阅读:
    预防新型冠状病毒科普宣传网站
    四则运算
    结对审查
    最大子段和
    单元自动测试Junit
    浅谈过去,畅想未来
    第一次的结对编程
    代码审查
    单元测试
    junit4单元测试
  • 原文地址:https://www.cnblogs.com/cy0628/p/13823091.html
Copyright © 2020-2023  润新知