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 的編譯和運行時檢查。
問題十一
以下程序能否通過編譯?若能,輸出是?
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 為弱引用。