2017-09-06 78 views
7

爲什麼這個指針算術(沒有在這些指針後面讀寫數據)導致段錯誤?爲什麼指針算術是段錯誤的原因?

#![allow(dead_code,unused_variables)] 
use std::cell::Cell; 

struct Bar<T: ?Sized> { 
    a: Cell<usize>, 
    value: T, 
} 

unsafe fn foo<T: ?Sized>(v: &T) { 
    let fake: &Bar<T> = std::mem::zeroed(); 

    // segfault on this line 
    // we are not reading or writing uninitialized data behind the reference, 
    // but only doing pointer arithmetic. We are not reading or writing 
    // uninitialized vtable, but only copy the vtable pointer. 
    let fake_val = &fake.value; 
} 


fn main() { 
    use std::any::Any; 

    let some_ref: &Any = &42 as &Any; 
    unsafe { foo(some_ref) }; 
} 

On Playground

輸出:Segmentation fault

回答

10

生鏽,merely creating a dangling reference is undefined behavior!這允許編譯器圍繞引用執行一些積極的優化,否則將不可能。

在這種特殊情況下,編譯器生成code which calculates the offset for the field by using the align value in the vtable。所以它試圖解引用導致段錯誤的vptr。


要有懸擺指針,你不應該使用參考,但raw pointer。你可以沒有問題地懸掛原始指針!

let fake: *const Bar<T> = std::ptr::null(); 
+0

如果可以安全地使用一個帶有原始指針的懸掛指針,爲什麼它會顯示與OP代碼相同的行爲(例如,在調試中失敗,在Release中爲「有效」) https://play.rust-lang。 org /?gist = 23baa08f2c83609e002f33b3e2852543&version = stable – Timidger

+1

@Timidger因爲您取消引用了第二行中的指針。解引用懸掛指針當然是不允許的。所以你不能用'.'運算符得到指向該字段的指針。說實話,我不知道獲得這樣一個指向字段的指針的最佳方式是什麼。最好問另一個問題! :) –

1

您正在取消引用您指定的行上的空指針。你正確地引用了這個值,但在調試模式下,爲LLVM輸出的代碼非常愚蠢。嘗試在發佈模式下運行此操作,您會看到可能是優化程序將對您很友善,並且不會再發生段錯誤。

既然這是UB,請不要依賴於這個真正的代碼。

+4

可以說,如果程序在調試模式下編譯segfaults以防未定義的行爲,那肯定不會那麼愚蠢。 :P在Rust中,獲取對無效內容的引用與直接訪問該內容一樣非法,因此段錯誤是我們所希望的「最佳」後果之一。 –