聊一聊 Rust 的 stack overflow
2023/6/22 1:22:37
本文主要是介绍聊一聊 Rust 的 stack overflow,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
早上看到了这篇文章 智能指针有可能会让你的应用崩溃, 下面分析一下
会导致 stack overflow 的代码
struct Node<T> { val: T, next: Option<Box<Node<T>>>, } struct LinkedList<T> { head: Option<Box<Node<T>>>, } impl<T> LinkedList<T> { fn new() -> Self { Self { head: None } } fn push_front(&mut self, val: T) { let next = self.head.take(); self.head = Some(Box::new(Node { val, next })); } } fn main() { let mut list = LinkedList::new(); for i in 0..1000000 { list.push_front(i); } }
playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=dfb32796d46df05fd6bcc4855fc11ae1
输出的结果:
thread 'main' has overflowed its stack fatal runtime error: stack overflow timeout: the monitored command dumped core /playground/tools/entrypoint.sh: line 11: 8 Aborted timeout --signal=KILL ${timeout} "$@"
原文中给出了解释:
程序崩溃是因为LinkedList的智能指针头部的默认释放导致对下一个节点的递归调用,这不是尾递归的,无法优化。修复方法是手动覆盖LinkedList数据结构的析构函数方法,迭代地释放每个节点,而不需要递归。从某种意义上说,这违背了智能指针的目的——它们无法从程序员那里解放手动内存管理的负担。
但是这个解释还不够直观,也没有给出修复代码
接下来我们一起以更直白的方式看看这个 LinkedList 被 Drop 时到底发生了什么
我们先给 Node<T>
和 LinkedList<T>
加上 Drop trait
, 方便我们观察代码执行过程
struct Node<T> { val: T, next: Option<Box<Node<T>>>, } struct LinkedList<T> { head: Option<Box<Node<T>>>, } impl<T> LinkedList<T> { fn new() -> Self { Self { head: None } } fn push_front(&mut self, val: T) { let next = self.head.take(); self.head = Some(Box::new(Node { val, next })); } } impl<T> Drop for Node<T> { fn drop(&mut self) { println!("drop node begin"); let _ = self.next.take(); println!("drop node end"); } } impl<T> Drop for LinkedList<T> { fn drop(&mut self) { println!("drop linkedlist begin"); let _ = self.head.take(); println!("drop linkedlist end"); } } fn main() { let mut list = LinkedList::new(); for i in 0..1000000 { list.push_front(i); } println!("EOF"); }
playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=61f7aad2bf8ddcd133a146cd88744e97
查看执行结果:
thread 'main' has overflowed its stack fatal runtime error: stack overflow timeout: the monitored command dumped core /playground/tools/entrypoint.sh: line 11: 7 Aborted timeout --signal=KILL ${timeout} "$@"
跟原来的代码一致
查看标准输出:
EOF drop linkedlist begin drop node begin drop node begin drop node begin (...) drop node begin
省略处全部都是 drop node begin
, 可见我们的程序在链式调用 Node<T>
的 drop
函数。因为 drop
一个 Node
就是依次 drop
它内部的 fields
(val
和 next
),当所有 fields
被 drop
完了,这个 Node
结构也就算被释放了
问题就在它内部这个 next
,它是一个链条,更准确的说应该是一个套娃🪆或是洋葱🧅!而默认的 Drop
机制是从内部(fields
)向外层依次释放,当我需要剥掉最外层时,却要等它里面那一层先剥完,里面的一层又要等更里面的一层...... 当层数过多时就导致了上面的 stack overflow
知道了原因,改起来就简单了,要剥哪一层就直接剥,不要等其它层,看代码:
// 可以不写,因为 LinkedList<T> 的 drop 已经把这里的 next 置为 None 了,这里只是为了演示函数调用过程 impl<T> Drop for Node<T> { fn drop(&mut self) { println!("drop node begin"); let _ = self.next.take(); println!("drop node end"); } } impl<T> Drop for LinkedList<T> { fn drop(&mut self) { println!("drop linkedlist begin"); // let _ = self.head.take(); let mut node = self.head.take(); // Some(Box<Node>) while let Some(mut inner) = node { // inner is Box<Node{val, next: Some()}> node = inner.next.take(); // inner.next is None } // drop inner println!("drop linkedlist end"); } }
playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=b8cce86d16ee776516e14fd031e75c6c
查看输出结果:
EOF drop linkedlist begin drop node begin drop node end drop node begin drop node end (...) drop linkedlist end
可见我们的套娃已经被一层层剥开了
问题
这个 LinkedList
的例子比较简单,对于它的 stack overflow
我们仔细一推敲就能找到问题所在。但是如果我们的程序运行了一年半载都没问题,忽然有一天就 stack overflow
了,并且还没啥线索,这个时候该咋整,想想都让人头大。
所以,能否在 stack overflow
发生时获取到函数调用栈(backtrace
) 呢?
带着这个问题一顿搜索, 找到两个讨论这个问题的链接:
How to diagnose a stack overflow
issue’s cause?
Great stack overflow error messages
后面这个 issue 中有人给出了一个 crate: backtrace-on-stack-overflow, 目前这个 crate 不支持 Windows
λ bat src/main.rs fn main() { unsafe { backtrace_on_stack_overflow::enable() }; f(92) } fn f(x: u64) { f(x) } λ cargo run Finished dev [unoptimized + debuginfo] target(s) in 0.01s Running `target/debug/so` Stack Overflow: 0: backtrace_on_stack_overflow::handle_sigsegv at /home/matklad/p/backtrace-on-stack-overflow/src/lib.rs:33:40 1: <unknown> 2: so::f at src/main.rs:6 3: so::f at src/main.rs:7:5 4: so::f at src/main.rs:7:5 5: so::f at src/main.rs:7:5 6: so::f at src/main.rs:7:5 7: so::f at src/main.rs:7:5 8: so::f at src/main.rs:7:5 9: so::f at src/main.rs:7:5 10: so::f at src/main.rs:7:5
这篇关于聊一聊 Rust 的 stack overflow的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-05-04安装 VPrix Desktop 的系统要求-icode9专业技术文章分享
- 2024-05-01巧用 TiCDC Syncpoint 构建银行实时交易和准实时计算一体化架构
- 2024-05-01银行核心背后的落地工程体系丨Oracle - TiDB 数据迁移详解
- 2024-04-26高性能表格工具VTable总体构成-icode9专业技术文章分享
- 2024-04-16软路由代理问题, tg 无法代理问题-icode9专业技术文章分享
- 2024-04-16程序猿用什么锅-icode9专业技术文章分享
- 2024-04-16自建 NAS 的方案-icode9专业技术文章分享
- 2024-04-14ansible 在远程主机上执行脚本,并传入参数-icode9专业技术文章分享
- 2024-04-14ansible 在远程主机上执行脚本,并传入参数, 加上remote_src: yes 配置-icode9专业技术文章分享
- 2024-04-14ansible 检测远程主机的8080端口,如果关闭,则echo 进程已关闭-icode9专业技术文章分享