在 Rust 中生成悬空引用(而不是解除引用)如何导致未定义的行为,并且可以观察到吗?

Jon*_*fer 4 undefined-behavior rust

Rust 参考Rustonomicon都明确指出“生成”悬空引用是未定义的行为。

以这段代码片段为例:

fn main() {
    let p: std::ptr::NonNull<u8> = std::ptr::NonNull::dangling();
    #[allow(unused_variables)]
    let r: &u8 = unsafe { std::mem::transmute::<_, _>(p) };
}
Run Code Online (Sandbox Code Playgroud)

在操场上通过 Miri 运行它会产生:

error: Undefined Behavior: constructing invalid value: encountered a dangling reference (address 0x1 is unallocated)
 --> src/main.rs:4:27
  |
4 |     let r: &u8 = unsafe { std::mem::transmute::<_, _>(p) };
  |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered a dangling reference (address 0x1 is unallocated)
  |
  = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
  = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
  = note: BACKTRACE:
  = note: inside `main` at src/main.rs:4:27
Run Code Online (Sandbox Code Playgroud)

当然,Rust 语言可以根据创建者的需要自由定义 UB 一样多的东西。我想知道的是这个未定义行为规则的优点是什么(通常,UB是在一种语言中引入的,以允许优化或简化事情)。

然而,根据上面链接的参考,未定义的行为可能已经通过编写一个本地的悬空引用而被触发,而我无法想出它的用途。

所以:

  • Rust 中生成(而不是取消引用)悬空引用如何导致 UB?
  • 是否可以在不取消引用的情况下观察 UB?

Cha*_*man 13

事实上,仅仅创建一个无效引用就是UB,这意味着无效引用不能存在。这对于诸如循环不变代码运动之类的优化很重要。

以下面的代码为例:

fn foo(arr: &[u32], cond: &bool) -> u32 {
    let mut result = 0;
    for &v in arr {
        if *cond {
            result += v;
        }
    }
    result
}
Run Code Online (Sandbox Code Playgroud)

编译器可以通过将条件提升到开头来优化这一点,从而允许进一步优化,例如自动向量化:

fn foo(arr: &[u32], cond: &bool) -> u32 {
    let mut result = 0;
    if *cond {
        for &v in arr {
            result += v;
        }
    }
    result
}
Run Code Online (Sandbox Code Playgroud)

但如果*cond可以悬空,则此优化无效:*cond例如可以触发段错误,并且如果数组为空,则原始版本不会发生这种情况。


kmd*_*eko 6

参考文献必须始终有效。

“引用可能有效也可能无效,但取消引用无效引用是未定义的行为”相比,这是一个更容易推理和使用的规则。允许这种行为也没有任何好处;如果您想引用一个可能有效或无效的值,则有原始指针或MaybeUninit用于此目的。

有一个原则是“安全的 Rust 不会导致未定义的行为”(或者反身的“如果一组参数可能导致未定义的行为,那么它必须被标记unsafe”)。希望你能理解为什么人们希望这是真的,并努力保证它。如果这样的原则不存在,那么这个函数就不能保证它不会导致未定义的行为:

fn get(ref: &u8) -> u8 {
    *ref
}
Run Code Online (Sandbox Code Playgroud)

未定义的行为,并且unsafe有一套非常混乱的规则需要遵循,以确保你做对了。但规则和后果不应该泄漏到unsafeRust 中的非代码中。

在所有实践中,我无法想到简单地创建引用会导致理智的编译器引起问题的情况,因为如果创建了它但没有使用它,那么它就会被消除死代码(但话又说回来,编译器不理智)。因此,我认为该规则不是出于技术原因,而是出于务实的原因。