Skip to main content

智能指针

指针是一个宽泛的概念, 用于表示一个包含内存地址的变量, 这个地址指向某个数据. 在 Rust 中最常见的指针是引用, 引用会 borrow 它指向的值, 由于引用没有任何其他功能, 因此也没有额外开销.

和引用不同, 智能指针是一个数据结构, 能够实现指针的功能, 同时有额外的元数据以及能力.

Rust 标准库中提供了若干种智能指针, 比如引用计数指针, 允许一个值有多个 owner, 当所有 owner 都出作用域后, 会自动释放值. 和引用不同的是, 智能指针拥有值的所有权.

之前看到的 Vec<T>String 类型内部实际都是智能指针实现. 智能指针通常使用 struct 实现, 且一般都会实现 DerefDrop Trait. 其中 Deref 允许智能指针在使用时和引用一样. Drop 允许自定义值的释放方式.

Rust 标准库中提供了许多智能指针, 且其他的 library 也可以提供自己的智能指针, 因此下面主要介绍一些最常用的智能指针, 根据讲的内容, 后面也可以自己实现智能指针:

  • Box<T>: 在堆内存上分配值
  • Rc<T>: 允许值有多个 owner 的引用计数指针
  • Ref<T>RefMut<T>, 通过 RefCell<T> 访问, 允许在运行时而非编译时进行 borrow 规则检查.

此外, 还会讲内部可变(interior mutability)模式, 这个模式让不可变类型暴露一个修改内部值的 API. 另外在使用引用计数时还牵扯到循环引用的问题, 循环引用会导致内存泄露.

使用 Box<T> 在堆上分配内存

Box<T> 允许将数据存放到堆内存, 在栈上仅是堆上数据的指针.

Box 没有额外开销, 仅是值的存放位置不同, 在如下情况常使用 Box:

  • 当有一个数据类型的大小无法在编译时确定时
  • 当有大量数据需要转移所有权, 但不想将它们一一复制的时候.
  • 当需要获取一个用 Trait 表示类型的数据的所有权时(而非具体类型)

针对第一种情况, 通过递归数据类型的定义来演示. 第二种情况利用复制指针而非数据的形式来极大降低复制数据量. 第三种情况即 Trait Object 的使用.

Box 的语法如下:

fn main() {
// 在堆上分配一个 i32
let b = Box::new(5);
println!("b = {}", b);

// 当 b 出作用域后, 堆内存也会被释放, 因为栈上的 b 指针已经出栈了
}

当然在堆上存放一个 i32 并不常用, 下面来看一个实际的例子, 即递归数据类型的定义.

Rust 中如果要将数据存放到栈上, 在编译时就需要知道该类型的具体大小, 但递归数据类型是不确定大小的, 因此需要借助 Box<T> 来实现, Box 指针本身是大小确定的, 只是指向的值在堆上分配.

enum List {
Cons(i32, Box<List>),
Nil,
}

use crate::List::{Cons, Nil};

fn main() {
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
}

这样仅 list 在栈上存放, 且包含一个 box 指向堆上的下一个节点.

Box 实现了 Drop, 因此当 list 出作用域后, 会按指向的链依次释放所有的结点.

Deref Trait

通过实现 Deref Trait, 可以自定义在该类型数据上的解引用操作符 * 行为. 通过实现 Deref 可以让智能指针像普通引用那样去使用. 比如在接收引用的地方, 也可以传入智能指针去使用.

一般情况下, 使用普通引用时:

fn main() {
let x = 5;
// 栈上 x 的引用
let y = &x;

assert_eq!(5, x);
// 必须使用解引用才能得到实际数据, 因为无法比较 i32 和 &i32
assert_eq!(5, *y);
}

和上面的例子类似, 在使用 Box 时, 我们也可以像使用普通引用那样对智能指针解引用:

fn main() {
let x = 5;
let y = Box::new(x);

assert_eq!(5, x);
assert_eq!(5, *y);
}

要分析为什么智能指针 Box 上可以使用解引用, 我们先来实现一个类似 Box 的数据类型 MyBox<T>:

struct MyBox<T>(T);

impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}

为了简化讲解, 这里没有让 MyBox 的数据在堆上分配.

如果我们在 MyBox 上使用解引用 *, 会无法编译, 需要在 MyBox 上实现 Deref Trait, Deref 要求实现一个 deref 方法, 接收 &self, 且返回一个内部数据的引用:

use std::ops::Deref;

impl<T> Deref for MyBox<T> {
// 泛型协议的关联类型具体化
type Target = T;

// borrow self 并返回内部数据的引用
fn deref(&self) -> &Self::Target {
&self.0
}
}

这样实现后, MyBox<T> 就可以使用解引用操作符 * 了, 且解引用后获取的是内部的数据值.

编译器只允许普通引用的解引用, 自定义数据类型如果没有实现 Deref, 则无法像这样解引用后获取内部数据.

在使用 MyBox<T> 并进行解引用操作时,, 由于是实现了 Deref 的, Rust 实际是这样的: *(y.deref()), 即先在 MyBox 上调用 deref 获取数据引用后, 再解引用.

Rust 中有 Deref coercison, 即会自动将实现了 Deref 的类型进行解引用变换为另外一个类型. 比如在需要 &str 参数的函数传参时, 我们可以传入 &String 类型的参数, 由于 String 实现了 Deref 且返回的就是 &str, Rust 会自动调用 deref 将传入的 &String 转换为 &str. 比如对 MyBox 类型也是同理:

fn hello(name: &str) {
println!("Hello, {name}!");
}

fn main() {
let m = MyBox::new(String::from("Rust"));
// 由于 MyBox 实现了 Deref, 当传入 &m 时, Rust 会 deref 获取内部的 &String, 再将 &String deref 得到 &str
hello(&m);
}

上面也可以看到, 自动解引用会自动进行直到获取到对应的类型, 解引用是在编译期就确定的, 因此不会在运行时有任何开销.

试想如果 Rust 没有自动的解引用, 我们上面的代码就要写成如下, 是多冗长...:

fn main() {
let m = MyBox::new(String::from("Rust"));
// 如果 Rust 没有自动解引用的话, 就要这样写了... 即 *m 获取 Stirng, 再取得 &String, 再切片...
hello(&(*m)[..]);
}

当自动解引用需要获取可变引用 &mut 时, 需要实现 DerefMut 来重写 * 解引用行为.

Rust 会对如下三种情况的数据类型和 trait 实现情况进行自动解引用:

  • &T 转换到 &U: 当 T 实现了 Deref<Target=U>
  • &mut T 转换到 &mut U: 当 T 实现了 DerefMut<Target=U>
  • &mut T 转换到 &U: 当 T 实现了 Deref<Target=U>

前两个引用转换都非常好理解, 不可变 -> 不可变, 以及 可变 -> 可变.

第三个情况时, 是 可变 -> 不可变. 反向转换是不可能的, 因为有 borrow 规则的限制.

使用 Drop Trait

当需要在类型 Owner 出作用域后自动释放, 需要实现 Drop, 且任意类型都可以实现, 比如释放网络连接或关闭文件.

在智能指针这里介绍 Drop 是因为基本上所有智能指针都通过实现 Drop 来对值进行自动释放, 比如当 Box 出作用域后自动释放堆内存.

编译器在遇到实现 Drop 的类型后, 会自动在合适位置插入 drop 调用.

下面是一个 CustomSmartPointer 的例子:

struct CustomSmartPointer {
data: String,
}

impl Drop for CustomSmartPointer {
// 实现自定义的 Drop, 这里仅 print 而没有其他操作
fn drop(&mut self) {
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
}
}

fn main() {
let c = CustomSmartPointer {
data: String::from("my stuff"),
};
let d = CustomSmartPointer {
data: String::from("other stuff"),
};
println!("CustomSmartPointers created.");

// 这里 c 和 d 出作用域后, 会自动调用 drop, 从而打印对应消息
// 且由于 c 和 d 在栈上的先后关系(此处 d 在栈顶), 因此会先打印 d 的, 再打印 c 的.
}

如果我们想手动触发 drop 调用, 则需要使用 std::mem::drop 方法, 而不能直接调用 Drop Trait 提供的 drop 方法. Rust 不允许开发者主动调用 drop 方法, 原因是编译器仍然会自动插入 drop 调用, 手动调用后会出现 double free 的问题.

如果在某些情况下想手动触发 drop, 则需要使用 std::mem 的 drop 函数, 因为此函数是默认引入的, 因此可以直接调用而不用引入:

fn main() {
let c = CustomSmartPointer {
data: String::from("some data"),
};
println!("CustomSmartPointer created.");
// 手动调用 std::mem::drop, 它会触发实际的 drop 方法调用, 因此这里会对应打印信息
drop(c);
println!("CustomSmartPointer dropped before the end of main.");
}

两个重要协议 DerefDrop 讲解完毕, 还看了 Box 的原理, 下面来看看 Rust 中其他的常用智能指针.

引用计数智能指针 Rc<T>

前面所有的内容都是在看一个值对应一个 Owner 的情况, 但有时候我们想一个值有多个 Owner, 这样可以在共享值时避免无谓的复制操作, 且有些数据结构中本质上某些值肯定有多个 Owner 的, 比如树或者是图的结点, 结点被多条边拥有, 且只有在所有边都没有后才会释放结点.

在 Rust 中这样的场景可以通过 Rc<T> 实现. 类比一家人在房间里看电视, 一个人走进来打开电视在看, 后面的人进来也可以看, 只有最后一个人走了再把电脑关上, 而不是走一个人关一次电视.

需要注意 Rc<T> 指向堆内存分配的数据, 且只能在单线程场景中使用(非线程安全).

下面是之前 List 的扩展, 使用 Rc<T> 来指向下一个结点:

enum List {
Cons(i32, Rc<List>),
Nil,
}

use crate::List::{Cons, Nil};
use std::rc::Rc;

fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
let b = Cons(3, Rc::clone(&a));
let c = Cons(4, Rc::clone(&a));

/* 通过上述就实现了一个这样的内存数据关系:
b --- \
---> a
c --- /
*/
}

代码中 Rc::clone 仅增加引用计数, 而不是将数据克隆. 在编写代码时通常使用 Rc::clone 而非调用 Rcclone 方法, 因为更可读(虽然效果都一样), 可以避免将 clone 误解为数据的深复制, 这样当出现性能问题去查找 clone 调用时, 也可以直接将 Rc::clone 跳过.

可以通过 strong_countweak_count 来获取强引用和弱引用个数, 通过代码可以发现 Rc::clone 会增加引用计数, 而当 drop 被自动调用后, 会减少引用计数, 当计数到 0 时, 值被自动清理.

另外注意 Rc<T> 允许值的多个不可变引用在多个 owner 共享, 而不能对可变引用进行共享, 否则会违反 borrow 规则. 但可变数据的共享是非常重要的, 因此 Rust 提供了 RefCell<T>, 允许在运行时检查 borrow 规则而非编译时.

RefCell<T> 和 Interior Mutability(内部可变)模式

内部可变模式允许代码中对不可变引用去改变它的内部值. Rust 正常情况下是不会允许这样做的(因为 borrow 规则限制).

在 Rust 中, 我们使用内部可变模式后, 就是对编译器说我们自己来在运行时保证 borrow 规则.

下面先来看 RefCell<T>, 再回头总结内部可变模式.

使用 RefCell<T> 在运行时保证 borrow 规则

RefCell 对它包裹的数据是单一所有权, 结合之前说到的 borrow 规则:

  • 在任意时刻同一个值只能有一个可变引用, 或有多个不可变引用, 不能同时有可变和不可变引用
  • 引用必须是有效的

结合规则对比 RefCellBox, 其中 Box 使用时仍然在编译时确保 borrow 规则, 而 RefCell 是在运行时确保 borrow 规则. 如果违反规则, Box 编译出错, 而 RefCell 的场景下是程序 panic 然后退出.

一般来说, 编译时检查是最好的选择, 可以提前暴露问题. 但某些场景下的编译时检查是不可能的, 当开发者确认 borrow 规则在运行时是正确的, 且编译器无法对这样的场景进行检查时, 就可以使用 RefCell.

RefCell 只能用在单线程场景下(非线程安全), 且即便 RefCell 自身是不可变的, 其内部值仍然是可变的.

而内部可变模式也正是如此, 即在不可变值中改变它内部数据.

内部可变模式的例子: Mock 对象

比如我们想创建一个库来追踪某个数据的值以及和最大值还差多少(比如方法或 API 的调用次数), 接近指定阈值后发送消息:

pub trait Messenger {
fn send(&self, msg: &str);
}

pub struct LimitTracker<'a, T: Messenger> {
messenger: &'a T,
value: usize,
max: usize,
}

impl<'a, T> LimitTracker<'a, T>
where
T: Messenger,
{
pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {
LimitTracker {
messenger,
value: 0,
max,
}
}

pub fn set_value(&mut self, value: usize) {
self.value = value;

let percentage_of_max = self.value as f64 / self.max as f64;

if percentage_of_max >= 1.0 {
self.messenger.send("Error: You are over your quota!");
} else if percentage_of_max >= 0.9 {
self.messenger
.send("Urgent warning: You've used up over 90% of your quota!");
} else if percentage_of_max >= 0.75 {
self.messenger
.send("Warning: You've used up over 75% of your quota!");
}
}
}

Messenger 中有一个方法是 send, 拿到 self 的不可变引用和一个消息 msg. 另外一个则是 LimitTrackerset_value 方法, 拿到 self 的可变引用.

如果在测试方法中这样写:

pub trait Messenger {
fn send(&self, msg: &str);
}

pub struct LimitTracker<'a, T: Messenger> {
messenger: &'a T,
value: usize,
max: usize,
}

impl<'a, T> LimitTracker<'a, T>
where
T: Messenger,
{
pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {
LimitTracker {
messenger,
value: 0,
max,
}
}

pub fn set_value(&mut self, value: usize) {
self.value = value;

let percentage_of_max = self.value as f64 / self.max as f64;

if percentage_of_max >= 1.0 {
self.messenger.send("Error: You are over your quota!");
} else if percentage_of_max >= 0.9 {
self.messenger
.send("Urgent warning: You've used up over 90% of your quota!");
} else if percentage_of_max >= 0.75 {
self.messenger
.send("Warning: You've used up over 75% of your quota!");
}
}
}

#[cfg(test)]
mod tests {
use super::*;

struct MockMessenger {
sent_messages: Vec<String>,
}

impl MockMessenger {
fn new() -> MockMessenger {
MockMessenger {
sent_messages: vec![],
}
}
}

impl Messenger for MockMessenger {
fn send(&self, message: &str) {
// 由于是不可变 self 中的不可变成员 sent_messages, 是无法这样用的, 会编译不过
self.sent_messages.push(String::from(message));
}
}

#[test]
fn it_sends_an_over_75_percent_warning_message() {
let mock_messenger = MockMessenger::new();
let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);

limit_tracker.set_value(80);

assert_eq!(mock_messenger.sent_messages.len(), 1);
}
}

上述代码中由于 send 方法中的写法, 编译不过, 因此可以使用 RefCell:

pub trait Messenger {
fn send(&self, msg: &str);
}

pub struct LimitTracker<'a, T: Messenger> {
messenger: &'a T,
value: usize,
max: usize,
}

impl<'a, T> LimitTracker<'a, T>
where
T: Messenger,
{
pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {
LimitTracker {
messenger,
value: 0,
max,
}
}

pub fn set_value(&mut self, value: usize) {
self.value = value;

let percentage_of_max = self.value as f64 / self.max as f64;

if percentage_of_max >= 1.0 {
self.messenger.send("Error: You are over your quota!");
} else if percentage_of_max >= 0.9 {
self.messenger
.send("Urgent warning: You've used up over 90% of your quota!");
} else if percentage_of_max >= 0.75 {
self.messenger
.send("Warning: You've used up over 75% of your quota!");
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use std::cell::RefCell;

struct MockMessenger {
// 内部可变模式的使用
sent_messages: RefCell<Vec<String>>,
}

impl MockMessenger {
fn new() -> MockMessenger {
MockMessenger {
sent_messages: RefCell::new(vec![]),
}
}
}

impl Messenger for MockMessenger {
fn send(&self, message: &str) {
// 在不可变 self 中获取内部可变模式成员的可变引用, 这样就能正常操作了
self.sent_messages.borrow_mut().push(String::from(message));
}
}

#[test]
fn it_sends_an_over_75_percent_warning_message() {
// --snip--
let mock_messenger = MockMessenger::new();
let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);

limit_tracker.set_value(80);

// 通过 borrow 获取不可变引用
assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);
}
}

上述代码就可以正常工作了.

RefCell<T> 可以理解为运行时的 borrow 检查, 普通引用有 &&mut, 普通引用会在编译时检查 borrow 规则, 而 RefCell 提供在运行时检查的能力, 对应 borrowborrow_mut 两个方法, 其中 borrow 返回 Ref<T>, borrow_mut 返回 RefMut<T>, 二者均实现了 Deref, 因此可以像普通引用那样使用.

在运行时, RefCell 可以追踪在其上创建的 RefRefMut 数量, 比如每次创建 Ref 就会把不可变引用的个数 +1, Ref 出作用域后, 个数 -1. RefMut 也同理, 这样可以在运行时看到有多少个不可变引用和可变引用, 从而对 borrow 规则进行检查. 如果规则被违反, 则会 panic, 比如下面的代码:

impl Messenger for MockMessenger {
fn send(&self, message: &str) {

// 违反 borrow 规则, 不能同时存在两个可变引用, 程序会崩溃
let mut one_borrow = self.sent_messages.borrow_mut();
let mut two_borrow = self.sent_messages.borrow_mut();

one_borrow.push(String::from(message));
two_borrow.push(String::from(message));
}
}

RcRefCell 结合实现多拥有者的内部可变模式

在前面讲 Rc 时就说过, 单独用 Rc 只会让一个值有多个拥有者, 每个拥有者都只能得到值的不可变访问(只读). 如果要多拥有者的可变值, 则需要结合 RcRefCell, 比如前面的 List 例子可以改写为:

use crate::List::{Cons, Nil};   // 方便使用
use std::cell::RefCell;
use std::rc::Rc;

#[derive(Debug)]
enum List {
Cons(Rc<RefCell<i32>>, Rc<List>),
Nil,
}

fn main() {
let value = Rc::new(RefCell::new(5));

let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));

let b = Cons(Rc::new(RefCell::new(3)), Rc::clone(&a));
let c = Cons(Rc::new(RefCell::new(4)), Rc::clone(&a));

// 内部可变模式的多拥有者值的可变引用
*value.borrow_mut() += 10;

println!("a after = {:?}", a);
println!("b after = {:?}", b);
println!("c after = {:?}", c);
}

在某些情况下为了换取数据结构的简化实现, 可以使用 RefCell 去进行. 但要注意上述代码只适合单线程环境, 多线程下 RefCell 对应的是 Mutex, 后续再讲.

避免循环引用造成内存泄露

在使用 Rc 时, 如果两个值互相持有时, 就可能出现循环引用.

比如下面的代码, 我们使用的 List 定义和之前的有所区别, 通过 RefCell 表示 Rc 是内部可变的.

use crate::List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;

#[derive(Debug)]
enum List {
Cons(i32, RefCell<Rc<List>>),
Nil,
}

impl List {
fn tail(&self) -> Option<&RefCell<Rc<List>>> {
match self {
Cons(_, item) => Some(item),
Nil => None,
}
}
}

当我们在 main 函数中使用时:

fn main() {
let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));

println!("a initial rc count = {}", Rc::strong_count(&a));
println!("a next item = {:?}", a.tail());

let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));

println!("a rc count after b creation = {}", Rc::strong_count(&a));
println!("b initial rc count = {}", Rc::strong_count(&b));
println!("b next item = {:?}", b.tail());
println!("a next item = {:?}", a.tail());

// 此处的 link 实际是 RefCell { value: Nil }, 是一个代码错误
// 但因此进入后会造成循环引用, 将 a 指向了 b
if let Some(link) = a.tail() {
*link.borrow_mut() = Rc::clone(&b);
}

println!("b rc count after changing a = {}", Rc::strong_count(&b));
println!("a rc count after changing a = {}", Rc::strong_count(&a));

// Uncomment the next line to see that we have a cycle;
// it will overflow the stack
// println!("a next item = {:?}", a.tail());
}

实际代码情况是这样的:

如果尝试在循环引用情况下打印 a.tail(), Rust 会去找 a 指向 b 再指向 a 再指向 b 这样循环往下直到栈溢出.

在实际代码中应时刻关注这类 Rc 嵌套的情况, 避免出现循环引用.

此外还有一个办法是使用 Rc::downgrade 来获取 Weak<T> 智能指针, 即弱引用. 弱引用不会对引用计数增加. 在弱引用的情况下, 由于可能在访问的时候值已经被释放了, 所以需要提前检查, 办法是使用 Rc::upgrade, 这样可以获取一个 Option<Rc<T>>, 从而可以判断是否还有值存在.

下面通过一个树的例子来看.

实现一个树结构

树的结点定义如下:

use std::cell::RefCell;
use std::rc::Rc;

#[derive(Debug)]
struct Node {
value: i32,
children: RefCell<Vec<Rc<Node>>>,
}

在其中使用 RefCell 简化了结构的实现, 这样可以在需要改变孩子数组时直接获取可变引用. 整个定义是使用 value 来存放值, 使用 children 来通过内部可变模式存放结点的孩子数组. 而孩子数组中的每个结点都是多所有者的(避免当 Vec 释放后所有 Node 都被自动释放). 我们希望 Node 是它孩子的所有者, 但同时又想能自由增删孩子, 因此用 RefCell 包裹 Vec. 我们想共享每个孩子的的所有权, 因此会需要使用 Rc<Node>.

使用时:

fn main() {
let leaf = Rc::new(Node {
value: 3,
children: RefCell::new(vec![]),
});

let root = Rc::new(Node {
value: 5,
children: RefCell::new(vec![Rc::clone(&leaf)]),
});
}

上述 root 也是一个 Node, 通过 Rc 在堆上创建, 孩子数组中包含一个刚才创建的 leaf. 而 leaf 在创建后就有了两个 owner, 即 leaf 和 root 中的 children 数组.

不过上述实现中, 我们还没有从孩子访问到 parent 的渠道, 下面继续来实现, 在 Node 定义中添加 parent:

use std::cell::RefCell;
use std::rc::{Rc, Weak};

#[derive(Debug)]
struct Node {
value: i32,
parent: RefCell<Weak<Node>>,
children: RefCell<Vec<Rc<Node>>>,
}

此时 parentRefCell 包裹的 Weak, 即我们可以通过内部可变去改变 Node 的 parent, 且由于使用 Weak, 因此 parent 不会改变 Node 的引用计数, 也不会造成循环引用.

在使用时:

fn main() {
let leaf = Rc::new(Node {
value: 3,
parent: RefCell::new(Weak::new()), // RefCell 包裹的空弱引用指针
children: RefCell::new(vec![]),
});

// 打印为 None, 此时还没有 parent
println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());

let root = Rc::new(Node {
value: 5,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![Rc::clone(&leaf)]),
});

// 给 leaf 的 parent 赋值 root 的弱引用指针
*leaf.parent.borrow_mut() = Rc::downgrade(&root);

println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
}

这样处理后, 将 rootWeak 引用赋值给 leaf.parent, 方式是通过可变引用 borrow_mut() 然后解引用 * 后获取到 parent 的智能指针.

由于不存在循环引用, 打印也是正常的了. 这样也给我们提供了一个思路, 在判断循环引用时可以通过打印去看, 且可以通过 Rc::strong_countRc::weak_count 来看任意变量对应的引用计数个数.