为什么这些场景要编译并创建悬空指针



给定以下函数定义:

// A: Compiles with static lifetime
fn foo() -> &'static i32 {
let i = &42;
i
}
// B: Compiles with normal lifetime
fn bar<'a>() -> &'a i32 {
let i = &42;
i
}
// C: Does not compile but same as B
fn baz<'a>() -> &'a i32 {
let i = 42;
&i
}
// D: Does not compile either
fn qux() -> &i32 {
let i = &42;
i
}

我预计这4个都会编译失败,因为我们返回了对局部变量的引用,但令我惊讶的是,a和B都编译正确,而C和D却没有。

对于A:因为静态数据在程序的整个生命周期内都存在,并且这些数据在程序执行过程中是动态创建的(可能多次(,所以这里怎么可能使用'static

对于B:为什么这是允许的,'a的生存期绑定到什么?它也是隐含的'static吗?为什么在更改为String等类型时,此功能不再起作用?

对于C和D:当它们实际上与B相同时,为什么不进行编译?

这些都会产生悬空指针吗,还是幕后发生了什么?

它们是否会因为返回的值永远存在而导致内存泄漏?

将评论中所说的全部汇集在一个答案中:

案例A

fn foo() -> &'static i32 {
let i = &42;
i
}

这里,由于右值静态提升,42没有在堆栈上分配,而是被视为const变量。这意味着它实际上将与编译器生成的二进制文件一起提供,指向它的指针将是一个指针,指向与程序一样长的值(因为它是程序的一部分(。

案例B

fn foo<'a>() -> &'a i32 {
let i = &42;
i
}

这个例子与上一个非常相似,只是做了一点修改。而不是'static的生存期,我们希望这次是一个通用的'a。这是因为

根据'static'static: 'a
  • CCD_ 11在CCD_ 12中是协变的
  • 这意味着编译器(相当直观地(被允许";降级";当我要求它是时,一个始终有效的生命到一个有效的生命(因为这在某种程度上"包含在始终中"(。有关差异的更多信息,请参阅rustonomicon。

    案例C

    fn foo() -> &'static i32 {
    let i = 42;
    &i
    }
    

    再次,稍作修改。或者看起来是这样。最大的区别是,这次我们显式地将42绑定到函数的作用域。因此,它将在堆栈上分配,并在函数返回时释放,从而导致编译错误。这是您提到的常规禁止悬挂指针错误。

    案例D

    fn foo() -> &i32 {
    let i = &42;
    i
    }
    

    这并不是因为一个完全不同的原因而编译的。事实上,你甚至可以忘记函数的内容,它仍然不会编译:

    fn foo() -> &i32 {
    panic!()
    }
    

    这是因为,通常,所有的生存期都必须是显式的。在少数情况下,编译器有一些规则可以自动分配这些生存期,因此允许它们是隐式的(请注意,这并不意味着编译器会像处理类型一样计算出"正确"的生存期;它可能会产生"错误"的生活期,从而阻止程序编译(。确切的规则可以在参考资料中找到。还要注意的是,尽管我说过在一般情况下,寿命必须是明确的,但事实证明,在实践中,少数寿命省略规则是足够的,而且通常是正确的。方便的

    最新更新