从语义角度来看,Rust 中‘&mut’ noalias 的未定义行为是什么时候发生的?

Zz *_*Tux 5 unsafe llvm undefined-behavior rust

正如 Rust 参考文档所说

\n\n
\n

违反指针别名规则。&mut T 和 &T 遵循 LLVM\xe2\x80\x99s 作用域 noalias 模型,除非 &T 包含 UnsafeCell。

\n
\n\n

实在是太暧昧了。
\n我想知道 Rust 中 noalias 的未定义行为发生在什么时刻&mut

\n\n

是下面的任何一个,还是其他什么?

\n\n
\n
    \n
  1. 当定义两个&mut指向同一地址时?
  2. \n
  3. 当两个&mut指向同一地址的对象暴露于锈迹时?
  4. \n
  5. &mut当对指向任何其他地址的同一地址执行任何操作时&mut
  6. \n
\n
\n\n

例如,这段代码显然是 UB 的:

\n\n
unsafe {\n    let mut x = 123usize;\n    let a = (&mut x as *mut usize).as_mut().unwrap(); // created, but not accessed\n    let b = (&mut x as *mut usize).as_mut().unwrap(); // created, accessed\n    *b = 666;\n    drop(a);\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

但是如果我像这样修改代码怎么办:

\n\n
struct S<\'a> {\n    ref_x: &\'a mut usize\n}\n\nfn main() {\n    let mut x = 123;\n    let s = S { ref_x: &mut x }; // like the `T` in `ManuallyDrop<T>`\n    let taken = unsafe { std::ptr::read(&s as *const S) }; // like `ManuallyDrop<T>::take`\n    // at thist ime, we have two `&mut x`\n    *(taken.ref_x) = 666;\n    drop(s);\n    // UB or not?\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

第二个版本也是UB吗?\n第二个版本与std::mem::ManuallyDrop
的实现完全相同。如果第二个版本是UB,是否存在安全漏洞?std::mem::ManuallyDrop<T>

\n

Mat*_* M. 3

别名限制不是什么

实际上,同一个项目有多个别名是常见的。&mut T

最简单的例子是:

fn main() {
   let mut i = 32;
   let j = &mut i;
   let k = &mut *j;

   *k = 3;

   println!("{}", i);
}
Run Code Online (Sandbox Code Playgroud)

但请注意,由于借用规则,您无法同时访问其他别名。

如果你看看以下的实现ManuallyDrop::take

pub unsafe fn take(slot: &mut ManuallyDrop<T>) -> T {
    ptr::read(&slot.value)
}
Run Code Online (Sandbox Code Playgroud)

您会注意到,没有同时可访问的&mut T:调用函数重新借用,ManuallyDrop从而形成slot唯一可访问的可变引用。

为什么 Rust 中的别名定义如此不明确

实在是很暧昧。我想知道&mutRust 中 noalias 的未定义行为发生在什么时刻。

运气不好,因为正如Nomicon中所指定的:

不幸的是,Rust 还没有真正定义它的别名模型。

原因是语言团队希望确保他们得出的定义既安全(显然如此)、实用,又不会关闭可能的改进之门。这是一个艰巨的任务。

Rust不安全代码指南工作组仍在努力建立确切的边界,特别是 Ralf Jung 正在研究一种名为Stacked Borrows的别名操作模型。

注意:Stacked Borrows 模型是在 MIRI 中实现的,因此您只需在 MIRI 中执行代码即可根据 Stacked Borrows 模型验证您的代码。当然,Stacked Borrows 仍处于实验阶段,因此这并不能保证任何事情。

建议注意什么

我个人持谨慎态度。鉴于确切的模型未指定,规则不断变化,因此我建议尽可能采取更严格的解释。

因此,我将无别名规则解释为&mut T

在代码中的任何一点,如果其中一个是 ,则作用域中不应有两个可访问的引用对同一内存进行别名&mut T

也就是说,我认为在使别名无效(通过借用)的情况下,将a形成为另一个or位于范围内的&mut T实例是不正确的。T&T&mut T

它很可能过于谨慎,但至少如果别名模型最终比计划的更加保守,我的代码仍然有效。