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

♀尐吖头ヾ 提交于 2021-02-18 20:59:13

问题


&mut T and &mut T results in a compilation error; this is great, it's objectively wrong to borrow mutably twice.

Is *mut T and*mut T undefined behaviour or is this a perfectly valid thing to do? That is, is mutable pointer aliasing valid?

What makes it even worse is that &mut T and *mut T actually compiles and works as intended, I can modify a value through the reference, the pointer, and then the reference again... but I've seen someone say that it's undefined behaviour. Yeah, "someone said so" is the only information I have.

Here's what I tested:

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

and of course, the main point of question:

Note — Thanks to trentcl for pointing out this example actually causes a copy when creating p2. This can be confirmed by replacing u8 with a non-Copy type. The compiler then complains about a move. Sadly, this does not get me closer to the answer, only reminds me that I can get unintended behaviour without it being undefined behaviour, simply because of Rust's move semantics.

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

Both yield:

42

Does this imply that two mutable pointers pointing to the same location and being dereferenced at different times is not undefined behaviour?

I don't think testing this on compiler is a good idea to begin with, as undefined behaviour could have anything happen, even printing 42 as if nothing is wrong. I mention it anyway as this is one of things I tried, hoping to get an objective answer.

I have no clue how to write a test that could force erratic behaviour that would make it dead obvious that this doesn't work because it's not used as intended, if that's even possible to do so.

I'm aware that this is very likely to be undefined behaviour and break in a multithreaded environment no matter what. I would expect a more detailed answer than that, though, especially if mutable pointer aliasing IS NOT undefined behaviour. (This would in fact be awesome, because while I use Rust for reasons like everyone else - memory safety, to say the least... I expect to still retain a shotgun that I could point anywhere, without it being locked onto my feet. I can have aliased "mutable pointers" without blowing my feet off in C.)

This is a question about whether I can, not about whether I should. I want to dive head-on into unsafe Rust, just to learn about it, but it feels like there's not enough information unlike in "horrible" languages like C about what's undefined behaviour and what's not.


回答1:


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.

The rules of references (&T and &mut T) are simple:

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

There are no "rules of raw pointers". Raw pointers (*const T and *mut T) can alias anything, anywhere, or they can point to nothing at all.

Undefined behavior can happen when you dereference a raw pointer, implicitly or explicitly turning it into a reference. This reference still must obey the rules of references, even when the & isn't explicit in the source code.

In your first example,

unsafe { *p += 1; }

*p += 1; takes a &mut reference to *p in order to use the += operator, as if you had written

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

(The compiler does not actually use AddAssign to implement += for u8, but the semantics are the same.)

Because &mut *p is aliased by another reference, namely r, the first rule of references is violated, causing undefined behavior.

Your second example (since editing) is different because there is no reference to alias, only another pointer, and there are no aliasing rules that govern pointers. Therefore, this

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

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

in the absence of any other references to value, is perfectly sound.



来源:https://stackoverflow.com/questions/57364654/do-aliasing-mutable-raw-pointers-mut-t-cause-undefined-behaviour

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!