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

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

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

问题一

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

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

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

问题二

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

rust 复制代码
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 字节。

问题三

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

rust 复制代码
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

问题四

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

rust 复制代码
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 被销毁。

第四个是不被允许的。

问题六

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

rust 复制代码
use std::rc::Rc;

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

答:不能通过编译。

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

问题七

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

rust 复制代码
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 实现:

rust 复制代码
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:

rust 复制代码
    let v = BadRefCell(UnsafeCell::new(vec![1, 2, 3]));

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

A.

rust 复制代码
    drop(v.borrow_mut());
    drop(v.borrow_mut());

B.

rust 复制代码
    let v1 = v.borrow_mut();
    let v2 = v.borrow_mut();
    v1.push(4);
    v2.push(5);

C.

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

D.

rust 复制代码
    v.borrow_mut().push(0);
    let n = v.borrow_mut()[0];
    println!("{}", n);

答:C。

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

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

rust 复制代码
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 的编译和运行时检查。

问题十一

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

rust 复制代码
use std::rc::Rc;

fn main() {
    let r1 = Rc::new(0);
    let r4 = {
        let r2 = Rc::clone(&r1);
        Rc::downgrade(&r2)
    };
    let r5 =  Rc::clone(&r1);
    let r6 = r4.upgrade();
    println!("{} {}", Rc::strong_count(&r1), Rc::weak_count(&r1));
}

答:可以通过编译。输出 3 1。

r1、r2、r5 对其 Rc<T> 存在强引用,r4 为弱引用。

相关推荐
Mr -老鬼2 小时前
Rust适合干什么?为什么需要Rust?
开发语言·后端·rust
Mr -老鬼2 小时前
Rust与Go:从学习到实战的全方位对比
学习·golang·rust
superman超哥4 小时前
Context与任务上下文传递:Rust异步编程的信息高速公路
开发语言·rust·编程语言·context与任务上下文传递·rust异步编程
古城小栈5 小时前
Rust 已经自举,却仍需GNU与MSVC工具链的缘由
开发语言·rust
古城小栈16 小时前
Rust 迭代器产出的引用层数——分水岭
开发语言·rust
peterfei20 小时前
IfAI v0.2.8 技术深度解析:从"工具"到"平台"的架构演进
rust·ai编程
栈与堆1 天前
LeetCode-1-两数之和
java·数据结构·后端·python·算法·leetcode·rust
superman超哥1 天前
双端迭代器(DoubleEndedIterator):Rust双向遍历的优雅实现
开发语言·后端·rust·双端迭代器·rust双向遍历
福大大架构师每日一题1 天前
2026年1月TIOBE编程语言排行榜,Go语言排名第16,Rust语言排名13。C# 当选 2025 年度编程语言。
golang·rust·c#
superman超哥1 天前
精确大小迭代器(ExactSizeIterator):Rust性能优化的隐藏利器
开发语言·后端·rust·编程语言·rust性能优化·精确大小迭代器