&mut T
和&mut T
会导致编译错误,这很好,借用可变两次是客观错误。
*mut T
和*mut T
是否未定义行为或者说这是完全有效的事情?也就是说,可变指针别名是有效的吗?
更糟糕的是,&mut T
和*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);
}
当然,问题的主要点是:
注意 — 感谢 trentcl 提出 指出这个示例在创建 p2
时实际上会导致复制。可以通过将 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中,我可以有别名的“可变指针”,而不会炸掉我的脚。)
这是关于我是否能够做到的问题,而不是关于我是否应该这样做的问题。我想全力以赴地学习不安全的Rust,但感觉没有足够的信息,不像“可怕”的语言C中那样清楚什么是未定义的行为,什么不是。
*p
取一个&mut
引用才能执行+=
操作。当然,你不能“仅仅”移动一个(非Copy
)类型的值出一个*mut
指针,因为这样做比仅仅取消引用这个指针更加不安全 -- 你需要使用ptr::read
来完成。 - trentunsafe { &mut *p1 }
与&mut unsafe { *p1 }
是不同的。unsafe 块将位置表达式转换为值表达式,从而触发移动操作。 - Sven Marnach