rust 解决的野指针(dangling pointer), c++ 等语言存在的野指针问题

23 阅读3分钟

c++ 代码的野指针问题

  • 原理
      1. 类似vector 数据结构,内部指向堆内存的指针会因为扩容,导致堆内存指针修改位置。原有的堆内存被释放
      1. 如果在扩容前保存vector 内部对象指针,扩容后指针就会指向一块被释放的堆内存
      1. 后续分配新变量的堆内存的时候,可能会重用被释放的堆内存
  • 代码
#include <iostream>
#include <vector>
using namespace std;

int main()
{
    // 创建一个 vector,初始插入 3 个元素
    std::vector<int> v = {1, 2, 3};
    std::cout << "初始状态:\n";
    std::cout << "size: " << v.size() << ", capacity: " << v.capacity() << "\n"; // size=3, capacity 可能为 3(取决于编译器)
    std::cout << "元素地址:" << &v[0] << " (v[0] = " << v[0] << ")\n\n";

    // 保存指向第一个元素的外部指针(此时指向原内存)
    int *ptr = &v[0];
    std::cout << "外部指针初始指向:" << ptr << " (值:" << *ptr << ")\n\n";

    // 插入第 4 个元素,触发扩容(因为原 capacity 为 3,无法容纳 4 个元素)
    v.push_back(4);
    std::cout << "插入第 4 个元素后:\n";
    std::cout << "size: " << v.size() << ", capacity: " << v.capacity() << "\n"; // capacity 可能变为 6(GCC 通常扩容为原容量的 2 倍)
    std::cout << "新元素地址:" << &v[0] << " (v[0] = " << v[0] << ")\n\n";

    // 尝试通过原指针访问(此时 ptr 已成为悬挂指针)
    std::cout << "访问悬挂指针:\n";
    std::cout << "指针地址:" << ptr << ",值:" << *ptr << "\n"; // 未定义行为!可能输出垃圾值、崩溃或程序异常
    // 新变量分配堆内存
    std::vector<int> v2 = {4, 5};
    std::cout << "\n新元素v2地址:" << &v2[0] << " (v2[0] = " << v2[0] << ")\n\n";

    std::cout << "悬挂指针指向的内存,被新分配之后,访问悬挂指针:\n";
    std::cout << "指针地址:" << ptr << ",值:" << *ptr << "\n";

    return 0;
}

  • 输出结果如下:
ljl@ljl-lenovo:dangling$ g++ main.cpp -o main
ljl@ljl-lenovo:dangling$ ./main
初始状态:
size: 3, capacity: 3
元素地址:0x614c9d3332b0 (v[0] = 1)

外部指针初始指向:0x614c9d3332b0 (值:1)

插入第 4 个元素后:
size: 4, capacity: 6
新元素地址:0x614c9d3336e0 (v[0] = 1)

访问悬挂指针:
指针地址:0x614c9d3332b0,值:348771123

新元素v2地址:0x614c9d3332b0 (v2[0] = 4)

悬挂指针指向的内存,被新分配之后,访问悬挂指针:
指针地址:0x614c9d3332b0,值:4

rust 解决野指针问题,会在编译器就发现它

fn main() {
    let mut v = vec![1, 2, 3];
    let elem_ref = &v[0]; // 获取元素引用

    // 编译错误!无法在持有元素引用时修改 Vec(会导致引用失效)
    v.push(4);

    println!("{}", elem_ref); // 若允许,此处的 elem_ref 已失效
}

在编译错误如下

ljl@ljl-lenovo:dangling$ cargo build
   Compiling dangling v0.1.0 (/mnt/d/work/rust/ex/dangling)
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
 --> main.rs:6:5
  |
3 |     let elem_ref = &v[0]; // 获取元素引用
  |                     - immutable borrow occurs here
...
6 |     v.push(4);
  |     ^^^^^^^^^ mutable borrow occurs here
7 |
8 |     println!("{}", elem_ref); // 若允许,此处的 elem_ref 已失效
  |                    -------- immutable borrow later used here