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

Do aliasing mutable raw pointers (*mut T) cause undefined behaviour?

&mut T&mut T 导致编译错误;这很好,objective两次可变借用是错误的。

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

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

这是我测试的:

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);
}

当然还有问题的要点:

注意 — 感谢 trentcl 。这可以通过将 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 就好像没有错一样。无论如何我都会提到它,因为这是我尝试过的事情之一,希望得到 objective 答案。

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

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

这是关于我是否可以的问题,而不是关于我是否应该的问题。我想直面不安全的 Rust,只是为了了解它,但感觉没有足够的信息,不像 "horrible" 语言,比如 C,关于什么是未定义的行为,什么不是。

Author's note: The following is an intuitive explanation, not a rigorous one. I don't believe there is a rigorous definition of "aliasing" in Rust right now, but you may find it helpful to read the Rustonomicon chapters on references and aliasing.

rules of references&T&mut T)很简单:

  • At any given time, you can have either one mutable reference or any number of immutable references.
  • References must always be valid.

没有"rules of raw pointers"。原始指针(*const T*mut T)可以在任何地方为任何东西起别名,或者它们根本不指向任何东西。

当您 取消引用 原始指针时,可能会发生未定义的行为,隐式或显式将其转换为引用。此引用 仍然必须遵守引用规则 ,即使 & 在源代码中不明确。

在你的第一个例子中,

unsafe { *p += 1; }

*p += 1; 使用 &mut 引用 *p 以便使用 += 运算符,就好像你写了

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

(编译器实际上并没有使用AddAssignu8实现+=,但语义相同。)

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

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

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

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

在没有对 value 的任何其他引用的情况下,是完全正确的。