Rust使用裸指针时出现意外segfault段错误问题咨询
问题描述
我最初使用Rust实现了一个Heap(堆)结构,但运行过程中出现了奇怪的段错误,因此我将代码精简为如下可稳定复现该异常行为的最小示例:
use core::fmt::Debug; pub struct Node<T: Ord + Debug> { pub value: *const T, } pub struct MaxHeap<T: Ord + Debug> { pub root: *const Node<T>, } impl<T: Ord + Debug> MaxHeap<T> { pub fn push(&mut self, value: *const T) { self.root = &mut Node { value: value }; } } fn main() { let a = 124i64; let b = 124i64; let c = 1i64; let mut heap = MaxHeap { root: &mut Node { value: &a }, }; heap.push(&b); println!("{:?}", &c); unsafe { println!("{:?}", *(*heap.root).value); } }
运行上述代码得到的输出结果为:
1 Segmentation fault (core dumped)
我观察到一个反常现象:如果移除打印变量c的代码,程序就不会触发段错误,且可以正确打印堆根节点的值:
124
按照我的认知,针对变量c的任何操作都不会对heap实例的状态产生影响,但实际运行结果却与预期相悖,请问我在代码编写或Rust内存机制理解上存在什么疏漏?
问题原因
你的代码存在**悬空指针(野指针)**问题,属于典型的未定义行为,和变量c本身没有任何关系,段错误只是内存被复用后刚好触发的表象:
- 你在
push方法里写的self.root = &mut Node { value: value };,是在push函数的栈帧上创建了一个临时的Node实例,然后把这个临时值的栈地址存到了self.root里。 - 当
push函数执行结束返回时,这个临时Node所在的栈内存就会被释放、后续会被其他函数调用复用,此时self.root已经指向了无效的内存地址。 - 你移除打印c的代码时没有触发段错误,只是刚好这块被释放的栈内存还没被覆写,存的还是原来的值,属于"未定义行为碰巧跑对了"的情况,不是代码没问题。当你加了
println!打印c的逻辑时,println!宏内部的函数调用会占用这块刚释放的栈空间,把原来存的Node数据覆写掉,你再去解引用这个悬空指针就会读到乱值、触发段错误。 - 同理你初始化
heap时写的root: &mut Node { value: &a }也是一样的问题,这个临时Node在let mut heap = ...语句结束后就已经失效了,只是刚好内存没被覆写所以没立刻炸。
修复方案
不要直接把栈上临时值的地址存到裸指针里长期持有,如果你需要让Node的生命周期和Heap绑定,要么用Box把Node分配到堆上,要么用安全的引用方式搭配生命周期标注,不要滥用unsafe和裸指针。
参考修复示例:
use core::fmt::Debug; pub struct Node<T: Ord + Debug> { pub value: T, } pub struct MaxHeap<T: Ord + Debug> { pub root: Option<Box<Node<T>>>, } impl<T: Ord + Debug> MaxHeap<T> { pub fn new() -> Self { Self { root: None } } pub fn push(&mut self, value: T) { self.root = Some(Box::new(Node { value })); } } fn main() { let a = 124i64; let b = 124i64; let c = 1i64; let mut heap = MaxHeap::new(); heap.push(a); heap.push(b); println!("{:?}", &c); if let Some(root) = &heap.root { println!("{:?}", root.value); } }
内容的提问来源于stack exchange,提问作者Валентин Стайков
相关产品推荐
相关产品推荐

