Rust 学习笔记:关于智能指针的练习题
- Rust 学习笔记:关于智能指针的练习题
- 问题一
- 问题二
- 问题三
- 问题四
- 问题五
- 问题六
- 问题七
- 问题八
- 问题九
- 问题十
Rust 学习笔记:关于智能指针的练习题
参考视频:
- https://www.bilibili.com/video/BV1SJ9vYsEfR
- https://www.bilibili.com/video/BV1Q79vYdEGx
- https://www.bilibili.com/video/BV1Q79vYdEgo
- https://www.bilibili.com/video/BV1Rg9vYhExC
- https://www.bilibili.com/video/BV1dLRVYrEfQ
- 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 的编译和运行时检查。