在Rust构造函数中使用unsafe与指针出现异常行为的原因
Rust构造函数内unsafe块导致程序崩溃的问题
在Rust里碰到个诡异问题:程序能不能跑通,居然取决于构造函数里有没有放unsafe块。不确定这是Rust的Bug,还是能跑的那个只是凑巧,或者我漏了什么关键知识点。
失败案例
代码
fn main() { let mut xc = Bob::default(); let mut xp = Bob::default(); let mut x = Bob::new(Some(&mut xc), Some(&mut xp)); unsafe { (&mut *x.child.unwrap()).string = String::from("child"); (&mut *xp.child.unwrap()).string = String::from("middle"); } println!("{:?}", xc); println!("{:?}", x); } #[derive(Debug)] struct Bob { child: Option<*mut Bob>, parent: Option<*mut Bob>, string: String, } impl Bob { fn new(child: Option<*mut Bob>, parent: Option<*mut Bob>) -> Bob { let mut this = Bob { child, parent, string: String::from("Hello") }; if child.is_some(){ unsafe { child.unwrap().as_mut().unwrap().parent = Some(&mut this);} } if parent.is_some(){ unsafe { parent.unwrap().as_mut().unwrap().child = Some(&mut this);} } this } } impl Default for Bob { fn default() -> Bob { Bob { child: None, parent: None, string: String::from("Hello") } } }
运行结果
Bob { child: None, parent: Some(0x37b76ff7f8), string: "child" } Bob { child: Some(0x37b76ff8b8), parent: Some(0x37b76ff8f0), string: "thread 'main' panicked at 'failed printing to stdout: Windows stdio in console mode does not support writing non-UTF-8 byte sequences', library\std\src\io\stdio.rs:1019:9 stack backtrace: 0: std::panicking::begin_panic_handler at /rustc/8ede3aae28fe6e4d52b38157d7bfe0d3bceef225/library\std\src\panicking.rs:593 1: core::panicking::panic_fmt at /rustc/8ede3aae28fe6e4d52b38157d7bfe0d3bceef225/library\core\src\panicking.rs:67 2: std::io::stdio::print_to at /rustc/8ede3aae28fe6e4d52b38157d7bfe0d3bceef225/library\std\src\io\stdio.rs:1019 3: std::io::stdio::_print at /rustc/8ede3aae28fe6e4d52b38157d7bfe0d3bceef225/library\std\src\io\stdio.rs:1096 4: rust_allocators::main at .\src\main.rs:14 5: core::ops::function::FnOnce::call_once<void (*)(),tuple$<> > at /rustc/8ede3aae28fe6e4d52b38157d7bfe0d3bceef225\library\core\src\ops\function.rs:250 note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. error: process didn't exit successfully: `target\debug\rust-allocators.exe` (exit code: 0xc0000374, STATUS_HEAP_CORRUPTION) Process finished with exit code -1073740940 (0xC0000374)
可行替代方案
代码
fn main() { let mut xc = Bob::default(); let mut xp = Bob::default(); let mut x = Bob::new(Some(&mut xc), Some(&mut xp)); xp.child = Some(&mut x); // unsafe { Some(&mut xp as *mut Bob).unwrap().as_mut().unwrap().child = Some(&mut x);} // ALSO WORKS unsafe { (&mut *x.child.unwrap()).string = String::from("child"); (&mut *xp.child.unwrap()).string = String::from("middle"); } println!("{:?}", xc); println!("{:?}", x); } #[derive(Debug)] struct Bob { child: Option<*mut Bob>, parent: Option<*mut Bob>, string: String, } impl Bob { fn new(child: Option<*mut Bob>, parent: Option<*mut Bob>) -> Bob { let mut this = Bob { child, parent, string: String::from("Hello") }; if child.is_some(){ unsafe { child.unwrap().as_mut().unwrap().parent = Some(&mut this);} } //if parent.is_some(){ // unsafe { parent.unwrap().as_mut().unwrap().child = Some(&mut this);} //} this } } impl Default for Bob { fn default() -> Bob { Bob { child: None, parent: None, string: String::from("Hello") } } }
运行结果
Bob { child: None, parent: Some(0x990ecff628), string: "child" } Bob { child: Some(0x990ecff708), parent: Some(0x990ecff740), string: "middle" }
问题原因
问题出在悬垂指针上:
- 失败案例中,
Bob::new里的this是函数内部的栈临时变量,当new返回时,this会被移动到main函数的x变量中,原来this的栈地址直接失效。但你已经把这个失效的地址存在了xp.child里,后续访问这个指针时,操作的是已经被释放的栈空间,直接触发堆损坏和输出 panic。 - 可行方案里,你是在
x已经被正确初始化到main函数的栈上之后,再给xp.child赋值&mut x——这个地址是main栈上x的有效地址,生命周期覆盖了后续的所有操作,不会出现悬垂指针问题,因此程序能正常运行。
内容的提问来源于stack exchange,提问作者mcmah309
相关产品推荐
相关产品推荐

