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

当然,主要的问题是:

Note年的今天,感谢trentcl的pointing out this example actually causes a copy when creating 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就好像什么都没有错.无论如何,我都会提到它,因为这是我try 过的事情之一,希望得到一个客观的答案.

我不知道如何编写一个测试,该测试可能会强制执行不稳定的行为,这会让人非常明显,这不起作用,因为它没有按预期使用,如果这是可能的话.

我知道这很可能是未定义的行为,在多线程环境中无论发生什么都会中断.不过,我希望得到比这更详细的答案,尤其是如果可变指针别名不是未定义的行为.(事实上,这将是非常棒的,因为尽管我和其他人一样使用 rust 迹——至少可以说是内存安全性方面的原因……但我仍然希望保留一把猎枪,可以指向任何地方,而不用把它锁在我的脚上.我可以在C中使用别名"可变指针",而不用把脚炸掉.)

这是关于我是否can岁的问题,而不是关于我是否should岁.我想直面不安全的 rust 迹,只是为了了解它,但感觉没有足够的信息,不像C这样的"可怕"语言,关于什么是未定义的行为,什么不是.

推荐答案

作者注:以下是一个直观的解释,而不是严格的解释.我不相信现在Rust中对"别名"有一个严格的定义,但你可能会发现阅读Rustonomicon referencesaliasing的章节会有所帮助.

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

  • 在任何给定的时间,可以有一个可变引用,也可以有任意数量的不可变引用.
  • 引用必须始终有效.

没有"原始指针规则".原始指针(*const T*mut T)可以在任何地方别名任何东西,也可以完全不指向任何东西.

当你创建一个原始指针,隐式或显式地将其转换为引用时,可能会发生未定义的行为.即使源代码中没有明确的&,也可以引用still must obey the rules of references.

在你的第一个例子中,

unsafe { *p += 1; }

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

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

(编译器实际上并不使用AddAssign来实现+= for u8,但语义是相同的.)

因为&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的情况下,这是完全正确的.

Rust相关问答推荐

如何在rust中有条件地分配变量?

如何最好地并行化修改同一Rust向量的多个切片的代码?

Rust TcpStream不能在读取后写入,但可以在不读取的情况下写入.为什么?

原始数组数据类型的默认trait实现

使用Box优化可选的已知长度数组的内存分配

Rust 的多态现象.AsRef与Derf

如何将单个 struct 实例与插入器一起传递到Rust中的映射

Rust将String上的迭代器转换为&;[&;str]

Rust 并行获取对 ndarray 的每个元素的可变引用

Rust 1.70 中未找到 Trait 实现

为什么我们有两种方法来包含 serde_derive?

go 重并堆积MPSC通道消息

从光标位置旋转精灵

从 Rust 中的 if/else 中的引用创建 MappedRwLockWriteGuard

Some(v) 和 Some(&v) 有什么区别?

我什么时候应该使用特征作为 Rust 的类型?

为什么我不能克隆可克隆构造函数的Vec?

判断对象是 PyDatetime 还是 Pydate 的实例?

为实现特征的所有类型实现显示

如何制作具有关联类型的特征的类型擦除版本?