别名可变原始指针 (*mut T) 是否会导致未定义的行为?

2023-12-22

&mut T and &mut T导致编译错误;这太棒了,两次可变借用在客观上是错误的。

Is *mut T and*mut T未定义的行为还是这是完全有效的事情?也就是说,可变指针别名有效吗?

更糟糕的是&mut T and *mut T实际上编译并按预期工作,我可以通过引用、指针修改值,然后再次引用......但我看到有人说这是未定义的行为。是的,“有人这么说”是我所掌握的唯一信息。

这是我测试的:

fn main() {
    let mut value: u8 = 42;

    let r: &mut u8 = &mut value;
    let p: *mut u8 = r as *mut _;

    *r += 1;

    unsafe { *p += 1; }

    *r -= 1;

    unsafe { *p -= 1; }

    println!("{}", value);
}

当然,问题的要点是:

Note— 感谢 trentcl指出这个例子实际上在创建时会产生一个副本p2 https://stackoverflow.com/questions/57364654/is-mut-t-aliasing-in-rust-ub#comment101216866_57364654。这可以通过替换来确认u8与非Copy类型。然后编译器抱怨移动。遗憾的是,这并没有让我更接近答案,只是提醒我,我可能会得到意想不到的行为,而不会是未定义的行为,这仅仅是因为 Rust 的移动语义。

fn main() {
    let mut value: u8 = 42;

    let p1: *mut u8 = &mut value as *mut _;
    // this part was edited, left in so it's easy to spot
    // it's not important how I got this value, what's important is that it points to same variable and allows mutating it
    // I did it this way, hoping that trying to access real value then grab new pointer again, would break something, if it was UB to do this
    //let p2: *mut u8 = &mut unsafe { *p1 } as *mut _;
    let p2: *mut u8 = p1;

    unsafe {
        *p1 += 1;
        *p2 += 1;
        *p1 -= 1;
        *p2 -= 1;
    }

    println!("{}", value);
}

两者产量:

42

这是否意味着指向同一位置并在不同时间取消引用的两个可变指针不是未定义的行为?

我认为一开始就在编译器上测试这个不是一个好主意,因为未定义的行为可能会发生任何事情,甚至打印42好像什么事都没有一样。无论如何我提到它,因为这是我尝试过的事情之一,希望得到客观的答案。

我不知道如何编写一个测试,该测试可能会强制出现不稳定的行为,这会明显表明这是行不通的,因为它没有按预期使用,如果有可能这样做的话。

我知道这很可能是未定义的行为,并且无论如何都会在多线程环境中中断。不过,我希望得到比这更详细的答案,特别是如果可变指针别名不是未定义的行为。 (这实际上非常棒,因为虽然我使用 Rust 的原因和其他人一样——至少可以说是内存安全……我希望仍然保留一把可以指向任何地方的霰弹枪,而不会被锁在我的脚上。我可以使用别名“可变指针”,而不会在 C 中大吃一惊。)

这是一个关于我是否can,而不是关于我是否should。我想深入研究不安全的 Rust,只是为了了解它,但感觉没有足够的信息,不像 C 等“可怕”的语言,关于什么是未定义行为,什么不是。


作者注:以下是直观的解释,并不严谨。我不认为 Rust 目前对“别名”有严格的定义,但您可能会发现阅读 Rustonomicon 的章节很有帮助参考 https://doc.rust-lang.org/nomicon/references.html and aliasing https://doc.rust-lang.org/nomicon/aliasing.html.

The 参考文献规则 https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html#the-rules-of-references (&T and &mut T)很简单:

  • 在任何给定时间,您可以拥有一个可变引用或任意数量的不可变引用。
  • 参考文献必须始终有效。

不存在“原始指针规则”。原始指针 (*const T and *mut T) 可以在任何地方为任何东西起别名,或者它们可以根本不指向任何东西。

当您执行以下操作时,可能会发生未定义的行为解引用原始指针,隐式或显式地将其转换为引用。这个参考仍然必须遵守参考规则,即使当&源代码中没有明确说明。

在你的第一个例子中,

unsafe { *p += 1; }

*p += 1;需要一个&mut参考*p为了使用+=运算符,就像您写过的一样

unsafe { AddAssign::add_assign(&mut *p, 1); }

(编译器实际上并没有使用AddAssign实施+= for u8,但语义是相同的。)

Because &mut *p由另一个引用别名,即r,违反了引用的第一条规则,导致未定义的行为。

你的第二个例子(编辑后)是不同的,因为没有参考别名,只有另一个pointer,并且没有管理指针的别名规则。因此,这

let p1: *mut u8 = &mut value;
let p2: *mut u8 = p1;

unsafe {
    *p1 += 1;
    *p2 += 1;
    *p1 -= 1;
    *p2 -= 1;
}

在没有任何其他参考的情况下value,完全正确。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

别名可变原始指针 (*mut T) 是否会导致未定义的行为? 的相关文章

随机推荐