欢迎光临
我们一直在努力

第19节 野指针、悬空指针成因、危害与规避方案

承接上文:上一节我们学习new/delete动态内存,掌握栈、堆内存的生命周期。本节专门拆解野指针(悬空指针),理清它的几类产生场景、程序会出现什么现象,给出工程上可落地的规避手段,从根源减少内存崩溃bug。

学习说明

很多新手把野指针和空指针混淆,以为野指针就是空指针。

空指针:指针的值为nullptr,不指向任何有效内存,安全。 野指针/悬空指针:指针保存了一个无效内存地址,指向已经销毁/释放的内存,访问属于未定义行为,程序随机崩溃、乱码。

野指针是C++工程里最难排查的一类bug,现象随机、难以复现。本节总结全部高频产生场景,搭配代码示例,建立指针安全编码习惯。

本节学习目标

  • 分清空指针、野指针(悬空指针)三者概念差异
  • 掌握野指针四类核心产生场景
  • 理解野指针的危害与随机崩溃原理
  • 掌握工程上规避野指针的标准化方案
  • 学会指针安全编码规范
  • 一、基础概念区分

    • 空指针 nullptr:指针不指向任何内存。对空指针delete是安全的;不能解引用*nullptr。
    • 悬空指针(野指针):指针存储的地址对应的内存已经被销毁/释放,指针没有被置空。

      行业习惯:悬空指针一般特指堆内存delete后产生的无效指针;野指针是统称,包含悬空指针、越界地址、随机垃圾地址。

    二、野指针四大成因(带代码示例)

    场景1:堆内存delete之后,指针没有置空(最常见,悬空指针)

    #include <iostream>
    using namespace std;

    int main()
    {
    int* p = new int(100);
    cout << *p << endl;
    delete p;
    // p保存的地址还在,但这块堆内存已经释放!
    // p现在是悬空指针(dangling pointer),指向已释放的内存
    // *p = 200; // 未定义行为,随机崩溃、乱值
    p = nullptr; // 释放后置空,消除悬空指针
    return 0;
    }

    场景2:函数返回局部栈变量的地址/引用

    局部栈变量在函数结束时自动销毁,返回的地址变成野指针。

    int* getLocalAddr()
    {
    int val = 10; // 栈上局部变量
    return &val; // 函数退出,val销毁,返回野指针
    }

    int main()
    {
    int* p = getLocalAddr();
    // cout << *p; // 访问野指针,结果不可预测
    return 0;
    }

    场景3:指针未初始化,指针里面是内存残留的随机垃圾值

    局部指针变量如果不初始化,里面是内存残留的随机地址,天生就是野指针。

    #include <iostream>
    using namespace std;

    int main()
    {
    int* p; // 局部指针不初始化,存储随机垃圾地址,野指针
    // cout << *p; // 随机崩溃
    int* p2 = nullptr; // 定义时直接初始化为空指针
    return 0;
    }

    场景4:数组越界、指针偏移指向无效内存地址

    指针运算超出数组有效范围,指向不属于我们的内存。

    int main()
    {
    int arr[3] = {1,2,3};
    int* p = &arr[0];
    p = p + 10; // 偏移超出数组范围,p指向无效内存,野指针
    // cout << *p;
    return 0;
    }

    三、野指针的危害

  • 随机崩溃:不是每次访问都崩溃,程序可能跑很久才出错,极难复现和定位。
  • 脏数据篡改:刚好指向一块其他变量的内存,读写会悄悄修改别的变量值,逻辑错乱。
  • 破坏堆内存管理结构:改写堆的内存标记,后续new/delete直接报错。
  • 没有任何编译报错:编译器无法捕获野指针,属于运行期隐患。
  • 重点:野指针本身不会报错,对野指针进行解引用 *p 才会触发未定义行为。仅仅保存无效地址,不会报错。

    补充:工程上可以使用AddressSanitizer(ASan)等内存检测工具,在开发阶段捕获野指针访问。

    四、工程标准化规避方案

  • 指针定义时立刻初始化,默认赋值nullptr,杜绝未初始化野指针。
  • delete堆内存之后,马上将指针置为nullptr。
  • delete p;
    p = nullptr;

  • 禁止返回局部栈变量的地址或引用。如果需要向外返回数据,使用堆内存或者引用参数。
  • 指针使用前增加判断:解引用之前先判断指针不是nullptr。
  • if(p != nullptr)
    {
    cout << *p;
    }

  • 指针运算严格检查边界,不要超出数组有效区间。
  • 现代C++优先使用智能指针std::unique_ptr / std::shared_ptr,自动管理内存,从根源消除手动new/delete带来的悬空指针问题。
  • #include <memory>
    int main()
    {
    auto p = make_unique<int>(100);
    // 离开作用域自动释放内存,无需手动delete,不会产生悬空指针
    return 0;
    }

    重要提醒:上述第4点的判空操作只能防止空指针,无法判断野指针。野指针不是nullptr,所以这个校验不能完全杜绝野指针,只能做基础防护。真正杜绝野指针需要依赖第6点的智能指针方案。

    五、本节核心总结

  • 空指针nullptr:安全,不指向任何内存;野指针/悬空指针:指向已销毁内存,地址非空,极度危险。
  • 野指针四大来源:delete后未置空;返回栈局部变量地址;指针未初始化;指针越界偏移。
  • 野指针最大痛点:无编译报错,bug随机触发,排查成本极高。
  • 基础防护:指针初始化、delete后置空、使用前判空;终极方案:现代C++使用智能指针。
  • 思考题

  • nullptr是空指针,空指针是不是野指针?
  • 下面代码是否存在野指针?
  • int* test()
    {
    int a = 10;
    int* p = &a;
    return p;
    }

  • 下面代码会不会产生悬空指针?如果delete p之后没有置空,p是空指针还是野指针?
  • int* p = new int(5);
    delete p;

  • 判断:解引用前判断if(p != nullptr)可以彻底杜绝野指针问题。
  • 下期预告

    下一节:类与对象基础:class定义与public/private访问控制。

    赞(0)
    未经允许不得转载:171主机测评 » 第19节 野指针、悬空指针成因、危害与规避方案
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址