C++ 必须杜绝野指针和无效指针

37 阅读4分钟

为什么C++程序需要专门提出杜绝野指针 因为C++更加底层,不自带内存管理,不会自动完成垃圾回收,越界检查的处理。所以会出现野指针的问题。

  • 区别于Java/Python,C/C++更加“底层”,将内存管理的任务全权交给了程序员。这意味着可以做的事情有很多,也意味着需要做的事情有很多。C++底层不具备内存管理的功能意味着没有垃圾回收,越界检查,所以程序本身必须杜绝野指针。

野指针的危害:大概率程序崩溃,甚至系统级报错;不崩溃会导致伪正常。

  • 底层原因:野指针会盲目随机地指向内存。
  • 有三种情况:
    • 系统内核内存;
    • 其他程序的内存;
    • 当前程序的已释放/未分配的内存。
    其中只有野指针侥幸指向当前程序已释放的内存,或者指向当前程序未分配但未被系统占用的内存,就不会引发程序崩溃,而这种”伪正常“的隐含bug不报错的情况更危险。原因有二。
    • 一是因为非法访问内存导致此代码严格依赖当前的系统状态和内存使用情况,也就是说你换个时间换个设备再运行这个代码,就有可能报错,把代码交给了随机,这就是初学者会认为代码里有玄学的原因。
    • 二是因为这会引发脏读脏写脏数据,脏读代表程序员忘记或没有考虑到这个指针的野指针状态就直接对这个指针进行解引用,得到了这个内存里已失效的旧值,程序对这个数据做进一步的处理就没有参考意义;脏写代表这个野指针对一个内存写了数据,之后程序再用到这个内存就会使其他代码里涉及到的值莫名篡改,函数返回错误结果。这种野指针对脏指针读写数据的情况就是脏数据。

野指针与无效指针 野指针是无效指针的子集。 野指针特指已释放/已回收内存的指针或者从未初始化的指针; 无效指针是指所有无法安全解引用的指针,即无效指针指向的内存无合法访问权限。 比如数组越界指针就是无效指针但不是野指针。

(野指针主要强调指到哪里去了并不清楚。无效指针主要强调这个指针无法安全解引用。)

  • 杜绝野指针出现的方法 野指针会引发脏数据的不规范处理;所以需要杜绝野指针出现。 nulltptr是合法的无效指针,作用就是说明这个指针不指向任何内存。
    • 指针初始化(要么指向有效内存,要么置为nullptr);

    • delete 之后置空(delete p; p = nullptr;);

    • 禁用返回栈内存地址和引用:栈内存的生命周期由作用域决定。函数执行结束后,栈变量会被自动回收。这时再返回栈内存地址的指针就是野指针,解引用会触发未定义行为。与这种情况类似的,按引用返回一个栈变量是返回栈内存地址的变种,绝对禁止。因此直接返回值会比返回指针/引用的方式更安全。

    • 传入引用: 通过按引用传递入参的方式修改外部数据,避免了栈变量传输在不同作用域下的危险情况。

    • 用智能指针,采用堆内存而不是栈内存。

  • 杜绝解引用无效指针的方法 无效指针是不能被安全解引用的指针。需要杜绝无效指针被解引用。
    • 用 AddressSanitizer(-fsanitize=address)编译,检测越界指针、野指针解引用;
    • 如果需要修改内存中的数据,那这个指针就不能是指向常量的指针const T* X / T const* X;
    • 指针判空
          if(p != nullptr) { ... } //在p并非nullptr的情况才执行动作
          //对nullptr解引用是C++未定义行为,会触发程序崩溃。
      

总结:

  1. 为杜绝野指针
    • 指针初始化;
    • delete之后要置nullptr;
    • 禁用返回栈内存的指针和引用;
    • 用智能指针。
  2. 为杜绝无效指针
    • 编译检测;
    • 解引用之前指针判空。