早上看到了这篇文章 智能指针有可能会让你的应用崩溃, 下面分析一下

会导致 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(valnext),当所有 fieldsdrop 完了,这个 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的更多相关文章

  1. 如何优雅地在Stack Overflow提问?

    今天来给大家聊一聊 Stack Overflow,Stack Overflow 是什么呢? 什么是 Stack Overflow? Stack Overflow 是一个全球最大的技术问答网站,作为一个 ...

  2. Stack Overflow 排错翻译 - Closing AlertDialog.Builder in Android -Android环境中关闭AlertDialog.Builder

    Stack Overflow 排错翻译  - Closing AlertDialog.Builder in Android -Android环境中关闭AlertDialog.Builder 转自:ht ...

  3. Stack Overflow: The Architecture - 2016 Edition(Translation)

    原文: https://nickcraver.com/blog/2016/02/17/stack-overflow-the-architecture-2016-edition/ 作者:Nick Cra ...

  4. Stack Overflow: The Architecture - 2016 Edition

    To get an idea of what all of this stuff “does,” let me start off with an update on the average day ...

  5. Stack Overflow is a question and answer site

    http://stackoverflow.com/ _ Stack Overflow is a question and answer site for professional and enthus ...

  6. stack overflow错误分析

    stack overflow(堆栈溢出)就是不顾堆栈中分配的局部数据块大小,向该数据块写入了过多的数据,导致数据越界,结果覆盖了老的堆栈数据. 或者解释为 在长字符串中嵌入一段代码,并将过程的返回地址 ...

  7. 推荐一个网站Stack Overflow

    网站URL:http://stackoverflow.com 我是怎么知道这个网站的呢?其实这个网站非常出名的,相信许多人都知道.如果你不知道,请继续阅读: 一次我在CSDN上面提问,但是想要再问多几 ...

  8. Stack Overflow 2016最新架构探秘

    这篇文章主要揭秘 Stack Overflow 截止到 2016 年的技术架构. 首先给出一个直观的数据,让大家有个初步的印象. 相比于 2013 年 11 月,Stack Overflow 在 20 ...

  9. IE中出现 "Stack overflow at line" 错误的解决方法

    在做网站时遇到一个问题,网站用的以前的程序,在没有改过什么程序的情况下,页面总是提示Stack overflow at line 0的错误,而以前的网站都正常没有出现过这种情况,在网上找了一下解决办法 ...

  10. 为什么开发者热衷在Stack Overflow上查阅API文档?

    摘要:一项新研究跟踪了Android开发者的访问历史,发现开发者多达二分之一的文档是从Stack Overflow上获取到的,而Stack Overflow上的示例也多于官方指南,开发者通过搜索更多时 ...

随机推荐

  1. IDA 逆 WDF 驱动时的函数识别插件

    快一年没更新了,累,工作累,各种累,想换个工作,突然发现找不到合适的工作了,哎,自己往火坑里跳,怪不得别人. import idautils import idaapi import idc prin ...

  2. 京东获得店铺的所有商品API接口(item_search_shop-获得店铺的所有商品)

    京东获得店铺的所有商品API接口(item_search_shop-获得店铺的所有商品)接口展示说明及教程: 公共参数 名称 类型 必须 描述key String 是 调用key(必须以GET方式拼接 ...

  3. 【技巧存档】常用网站如CSDN打开时加载慢怎么办?

    找到最快站点,更改host文件 F12打开控制台,查看网络中哪些站点的请求标红,如 img-home.csdnimg.cn 去站长之家测试ping值,找到最低ping值的ip,这里找到安徽合肥,ip为 ...

  4. 碉堡!“万物皆可分”标记模型上线「GitHub 热点速览」

    这周有个让人眼前一亮的图像识别模型 segment-anything,它能精细地框出所有可见物体,它标记出的物体边界线清晰可见.如此出色的模型,自然获得了不少人的赞赏,开源没几天,就拿下了 18k+ ...

  5. SpringBoot项目中使用缓存Cache的正确姿势!!!

    前言 缓存可以通过将经常访问的数据存储在内存中,减少底层数据源如数据库的压力,从而有效提高系统的性能和稳定性.我想大家的项目中或多或少都有使用过,我们项目也不例外,但是最近在review公司的代码的时 ...

  6. day06 循环和数据类型的内置方法

    循环加数据类型的内置方法 while 循环 for循环 range关键字 数据类型的内置方法 字符串的内置方法 while循环 while + continue #打印0-10的数字不打印6 n=0 ...

  7. LINUX 下LAMP之yum方法部署环境

    一. 系统组成 系统由前台网站和后台保种服务两部分组成,其中后台保种可以扩展成多台设备,提高保种的资源量以及性能.下面安装以houtaiip代表后台保重服务器ip地址,qiantaiip代表前台网站i ...

  8. Python代码相似度计算(基于AST和SW算法)

    代码相似度计算将基于AST和Smith-Waterman算法 AST (抽象语法树) AST即Abstract Syntax Trees,是源代码的抽象语法结构的树状表示,树上的每个节点都表示源代码中 ...

  9. 10分钟极速入门dash应用开发

    本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/dash-master 大家好我是费老师,几天前我发布了由我开源维护的dash通用网页组件库fac的0 ...

  10. Vue的生命周期的详解

    Vue的生命周期   Vue的生命周期是每个使用Vue框架的前端人员都需要掌握的知识,以此作为记录.   Vue的生命周期就是vue实例从创建到销毁的全过程,也就是new Vue() 开始就是vue生 ...