Rust如何知道在堆栈展开期间是否运行析构函数? [英] How does Rust know whether to run the destructor during stack unwind?

查看:293
本文介绍了Rust如何知道在堆栈展开期间是否运行析构函数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

mem :: uninitialized mem :: uninitialized 指出了使用该函数的危险/不安全性:在未初始化的内存上调用 drop 是未定义的行为。

The documentation for mem::uninitialized points out why it is dangerous/unsafe to use that function: calling drop on uninitialized memory is undefined behavior.

所以我认为这段代码应该是未定义的:

So this code should be, I believe, undefined:

let a: TypeWithDrop = unsafe { mem::uninitialized() };
panic!("=== Testing ==="); // Destructor of `a` will be run (U.B)

但是,我编写了这段代码可以在安全的Rust中工作,并且似乎不会遭受不确定的行为:

However, I wrote this piece of code which works in safe Rust and does not seem to suffer from undefined behavior:

#![feature(conservative_impl_trait)]

trait T {
    fn disp(&mut self);
}

struct A;
impl T for A {
    fn disp(&mut self) { println!("=== A ==="); }
}
impl Drop for A {
    fn drop(&mut self) { println!("Dropping A"); }
}

struct B;
impl T for B {
    fn disp(&mut self) { println!("=== B ==="); }
}
impl Drop for B {
    fn drop(&mut self) { println!("Dropping B"); }
}

fn foo() -> impl T { return A; }
fn bar() -> impl T { return B; }

fn main() {
    let mut a;
    let mut b;

    let i = 10;
    let t: &mut T = if i % 2 == 0 {
        a = foo();
        &mut a
    } else {
        b = bar();
        &mut b
    };

    t.disp();
    panic!("=== Test ===");
}

似乎总是执行正确的析构函数,而忽略了另一个析构函数。如果我尝试使用 a b (例如 a.disp()而不是 t.disp()),它正确地错误地指出我可能正在使用未初始化的内存。令我惊讶的是,当 panic king时,无论 i 是。

It always seems to execute the right destructor, while ignoring the other one. If I tried using a or b (like a.disp() instead of t.disp()) it correctly errors out saying I might be possibly using uninitialized memory. What surprised me is while panicking, it always runs the right destructor (printing the expected string) no matter what the value of i is.

这是怎么发生的?如果运行时可以确定要运行哪个析构函数,则应该从 mem的文档中删除有关强制为实现 Drop 的类型初始化的内存部分。 :: uninitialized()如上面所链接?

How does this happen? If the runtime can determine which destructor to run, should the part about memory mandatorily needing to be initialized for types with Drop implemented be removed from documentation of mem::uninitialized() as linked above?

推荐答案

使用放置标志

Rust(直到版本1.12为止(包括该版本),在每个其类型实现 Drop 的值中存储一个布尔值标志(因此将该类型的大小增加一个字节)。该标志决定是否运行析构函数。因此,当您执行 b = bar()时,它会设置 b 变量的标志,因此只能运行 b 的析构函数。反之亦然,与 a

Rust (up to and including version 1.12) stores a boolean flag in every value whose type implements Drop (and thus increases that type's size by one byte). That flag decides whether to run the destructor. So when you do b = bar() it sets the flag for the b variable, and thus only runs b's destructor. Vice versa with a.

请注意,从Rust版本1.13开始(在编写本beta编译器时) ),该标志未存储在类型中,而是存储在每个变量或临时变量的堆栈中。 Rust编译器中MIR的出现使这成为可能。 MIR大大简化了Rust代码到机器代码的转换,从而使此功能能够将丢标移动到堆栈中。如果优化可以在编译时找出要删除哪个对象的时间,通常会消除该标志。

Note that starting from Rust version 1.13 (at the time of this writing the beta compiler) that flag is not stored in the type, but on the stack for every variable or temporary. This is made possible by the advent of the MIR in the Rust compiler. The MIR significantly simplifies the translation of Rust code to machine code, and thus enabled this feature to move drop flags to the stack. Optimizations will usually eliminate that flag if they can figure out at compile time when which object will be dropped.

您可以在Rust编译器的最新版本中观察该标志。 1.12通过查看类型的大小:

You can "observe" this flag in a Rust compiler up to version 1.12 by looking at the size of the type:

struct A;

struct B;

impl Drop for B {
    fn drop(&mut self) {}
}

fn main() {
    println!("{}", std::mem::size_of::<A>());
    println!("{}", std::mem::size_of::<B>());
}

打印 0 1 分别在堆栈标志之前,<< c $ c> 0 0

prints 0 and 1 respectively before stack flags, and 0 and 0 with stack flags.

使用 mem :: uninitialized 仍然是不安全的,因为编译器仍然看到分配给 a 变量并设置放置标记。因此,析构函数将在未初始化的内存上被调用。请注意,在您的示例中, Drop impl不会访问您类型的任何内存(drop标志除外,但您看不到它)。因此,您不访问未初始化的内存(由于类型是零大小的结构,因此大小始终为零字节)。据我所知,这意味着您的不安全{std :: mem :: uninitialized()} 代码实际上是安全的,因为此后不会发生内存不安全的情况。

Using mem::uninitialized is still unsafe, however, because the compiler still sees the assignment to the a variable and sets the drop flag. Thus the destructor will be called on uninitialized memory. Note that in your example the Drop impl does not access any memory of your type (except for the drop flag, but that is invisible to you). Therefor you are not accessing the uninitialized memory (which is zero bytes in size anyway, since your type is a zero sized struct). To the best of my knowledge that means that your unsafe { std::mem::uninitialized() } code is actually safe, because afterwards no memory unsafety can occur.

这篇关于Rust如何知道在堆栈展开期间是否运行析构函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆