Rust 学习笔记:关于智能指针的练习题

发布于:2025-06-06 ⋅ 阅读:(21) ⋅ 点赞:(0)

Rust 学习笔记:关于智能指针的练习题

参考视频:

  1. https://www.bilibili.com/video/BV1SJ9vYsEfR
  2. https://www.bilibili.com/video/BV1Q79vYdEGx
  3. https://www.bilibili.com/video/BV1Q79vYdEgo
  4. https://www.bilibili.com/video/BV1Rg9vYhExC
  5. https://www.bilibili.com/video/BV1dLRVYrEfQ
  6. https://www.bilibili.com/video/BV1RGRnYoEiJ

问题一

以下程序能否通过编译?若能,输出是?

fn main() {
    let mut n = 1;
    let b = Box::new(&mut n);
    **b += 1;
    
    println!("{}", n);
}

答:可以通过编译。输出为 2。

问题二

假设我们有一个程序,其中有一个变量:

let x = [Box<(usize, usize)>; 4] = /* ... */

对于一个 64 位架构的编译目标,x 在栈上所占用的最小内存大小是多少?

答:32 字节。

在 Rust 中,变量 x 的类型为 [Box<(usize, usize)>; 4],这是一个包含 4 个元素的数组,每个元素是一个 Box<(usize, usize)>。Box<T> 是一个智能指针,它在栈上仅存储一个指针,在 64 位架构上,指针的大小固定为 8 字节,故数组大小 = 4 * 8 = 32 字节。

问题三

以下程序能否通过编译?若能,输出是?

use std::ops::Deref;

#[derive(Copy, Clone)]
struct AccessLogger(i32);

impl Deref for AccessLogger {
    type Target = i32;
    fn deref(&self) -> &Self::Target {
        println!("deref");
        &self.0
    }
}

fn main() {
    let n = AccessLogger(-1);
    let x = *n + 1;
    let n2 = n;
    println!("{} {}", x, *n);
}

答:可以通过编译。输出为:

deref
deref
0 -1

问题四

以下程序能否通过编译?若能,输出是?

struct Example(i32);

impl Drop for Example {
    fn drop(&mut self) {
        self.0 += 1;
        println!("drop {}", self.0);
    }
}

fn main() {
    let e = Example(0);
    drop(e);
    drop(e);
}

答:不能通过编译。

问题五

在这里插入图片描述

答:{ s }、drop(s)、(|_|())(s)。

第一个利用了作用域,s 变量离开作用域时自动被清除。

第二个调用了 std::mem::drop 函数,显式销毁了 s 变量。

第三个是一个空闭包, 闭包获取了 s 的所有权,离开闭包时 s 被销毁。

第四个是不被允许的。

问题六

以下程序能否通过编译?若能,输出是?

use std::rc::Rc;

fn main() {
    let n = Rc::new(1);
    let mut n2 = Rc::clone(&n);
    *n2 += 1;
    println!("{}", n);
}

答:不能通过编译。

Rc::clone 是浅拷贝,并没有获取值的所有权。

问题七

以下程序能否通过编译?若能,输出是?

use std::rc::Rc;

struct Example;

impl Drop for Example {
    fn drop(&mut self) {
        println!("drop");
    }
}

fn main() {
    let x = Rc::new(Example);
    let y = Rc::clone(&x);
    println!("A");
    drop(x);
    println!("B");
    drop(y);
    println!("C");
}

答:可以通过编译。输出为:

A
B
drop
C

销毁 x 时,对 Example 的引用计数为 1。只有当 y 也被销毁时,引用计数才为 0,执行 drop 方法。

问题八

以下哪项最好地描述了 Rust 中内部可变性的概念?

A. 将 unsafe 代码包装在安全的 API 中
B. 允许借用检查器在运行时强制执行内存安全
C. 允许数据结构内部的数据被修改
D. 允许通过不可变引用修改数据

答:D。

问题九

在这里插入图片描述

答:RefCell<usize>。

问题十

考虑以下未检查内部值是否被借用的错误 RefCell 实现:

use std::cell::UnsafeCell;

struct BadRefCell<T>(UnsafeCell<T>);

impl<T> BadRefCell<T> {
    pub fn borrow_mut(&self) -> &mut T {
        unsafe { &mut *self.0.get() }
    }
}

假设我们有如下 BadRefCell:

    let v = BadRefCell(UnsafeCell::new(vec![1, 2, 3]));

以下哪个代码片段在使用此 API 时会违反内存安全?

A.

    drop(v.borrow_mut());
    drop(v.borrow_mut());

B.

    let v1 = v.borrow_mut();
    let v2 = v.borrow_mut();
    v1.push(4);
    v2.push(5);

C.

    let v1 = v.borrow_mut();
    let n = &v1[0];
    v.borrow_mut().push(0);
    println!("{}", n);

D.

    v.borrow_mut().push(0);
    let n = v.borrow_mut()[0];
    println!("{}", n);

答:C。

获取 v 的可变引用后,向其中插入数据,可能会变更值在堆上的位置。此时再访问 n,可能发生内存泄漏,使得 n 变成一个悬垂引用。

数组 [1, 2, 3] 太小了,插入 1 个元素不一定会导致位置变化。我们改用一个包含 100000 个元素的数组,完整代码如下:

use std::cell::UnsafeCell;

struct BadRefCell<T>(UnsafeCell<T>);

impl<T> BadRefCell<T> {
    pub fn borrow_mut(&self) -> &mut T {
        unsafe { &mut *self.0.get() }
    }
}

fn main() {
    let v = BadRefCell(UnsafeCell::new(vec![1; 10000]));
    
    let v1 = v.borrow_mut();
    let n = &v1[0];
    v.borrow_mut().push(0);
    println!("{}", n);
}

运行结果:

在这里插入图片描述

理论上应该打印 1。显然这段代码违反内存安全,但还是通过了 Rust 的编译和运行时检查。