【问题标题】:Pointer to first element of vector in Result<Vec<f64>, _> is corrupted指向 Result<Vec<f64>, _> 中向量第一个元素的指针已损坏
【发布时间】:2022-01-21 12:11:27
【问题描述】:

我有一个Result&lt;Vec&lt;f64&gt;, _&gt;。当我尝试提取指向实际f64 数组的指针时,我观察到dptr 指向的数组是预期数组的损坏版本(前10 个字节已更改)。

为什么会发生这种情况,我该如何避免?

use std::error::Error;

fn main() {
    let res: Result<Vec<f64>, Box<dyn Error>> = Ok(vec![1., 2., 3., 4.]);
    let dptr: *const f64 = match res {
        Ok(v) => &v[0], 
        Err(_) => std::ptr::null(),
    };
    assert_eq!(unsafe { *dptr }, 1.0);
}

结果:

thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `0.0`,
 right: `1.0`', src/main.rs:9:5

Playground

【问题讨论】:

  • 我添加了 minimal reproducible example 并链接到 Rust Playground 上的可运​​行版本。这将使人们很容易看到您为自己描述的内容。这是否准确地抓住了问题?如果没有,请随时更新代码和 Playground 链接。
  • 我认为这确实抓住了我的问题的要点,是的。谢谢!

标签: pointers rust ownership


【解决方案1】:

该程序的行为是未定义的,可以通过在 Miri 下运行它来查看,Miri 是一个 Rust 解释器,有时可以检测到未定义的行为。 (您可以在操场上通过单击“工具”(右上角)->“Miri”来执行此操作):

error: Undefined Behavior: pointer to alloc1039 was dereferenced after this allocation got freed
 --> src/main.rs:9:25
  |
9 |     assert_eq!(unsafe { *dptr }, 1.0);
  |                         ^^^^^ pointer to alloc1039 was dereferenced after this allocation got freed
  |
  = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
  = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information

这里发生的是释放后使用:Ok(v) =&gt; &amp;v[0], 行将数据从v(因此res)移动,导致它被释放。后来,用于其他变量的数据覆盖了现有数据(因为在其指向的内存被释放后使用指针是未定义的行为)。

如果您尝试在没有unsafe 的情况下以正常方式从res 中读取数据,那么您将遇到此问题的编译时错误:

error[E0382]: use of partially moved value: `res`
 --> src/main.rs:9:10
  |
6 |         Ok(v) => &v[0],
  |            - value partially moved here
...
9 |     dbg!(res.unwrap()[0]);
  |          ^^^ value used here after partial move
  |
  = note: partial move occurs because value has type `Vec<f64>`, which does not implement the `Copy` trait
help: borrow this field in the pattern to avoid moving `res.0`
  |
6 |         Ok(ref v) => &v[0],
  |            +++

(playground)

【讨论】:

  • 如果您将匹配臂更改为抓取第三个元素,那么它可以正常工作(Ok(v) =&gt; &amp;v[2])... 触发移动的索引 0 或 1 有什么特别之处?它仍然给出 Miri 错误,但我得到了预期的输出 (3.0)。
  • @jeremymeadows 编译器在释放内存时不会清除内存:只是碰巧在移动和断言之间没有很多数据存储到内存中,所以只有前两个元素被其他数据覆盖。但它仍然是未定义的行为:在这种情况下,编译器可以做它想做的任何事情。如果您打开优化,那么当assert 中使用第二个元素时确实会出现错误。
  • @jeremymeadows 一些分配器使用特定模式填充已释放块的初始部分,以便更容易检测损坏。也许 Rust 在你的系统上使用的分配器就是这样工作的。
  • @Smitop 对,我只是没想到会覆盖任何东西,因为我们没有使用任何新数据,尽管我想这不能保证,因为这绝对是要避免的无论如何。
  • @user4815162342 超级有趣,不知道曾经存在过
猜你喜欢
  • 2011-03-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-12
  • 2023-04-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多