点击上方蓝字 江湖评谈设为关注/星标
前言
众所周知,Rust是主打内存安全。在搞Rust的时候,会有这样一个疑问?C++既然已经引入了安全指针,那么Rust所谓的内存安全用处何在呢?岂不是功能重叠?
C++/Rust
1.普通安全指针
长期以来,Linux/Win上的各种漏洞大致归类指针悬空,栈溢出,双重释放等。这罪魁祸首即是C++的高度自由度导致的。Rust牺牲自由,换取安全。
但其实C++也是有安全指针的,C++独占指针std::unique_ptr:
int main() {
std::unique_ptr<int> ptr1 = std::make_unique<int>(10); // 创建一个 unique_ptr
std::cout << *ptr1 << std::endl; // 输出 10
// std::unique_ptr<int> ptr2 = ptr1; // 错误,unique_ptr 不允许拷贝
std::unique_ptr<int> ptr2 = std::move(ptr1); // 通过 std::move 转移所有权
if (!ptr1) {
std::cout << "ptr1 is null after move" << std::endl;
}
}
ptr1只能拥有一个对象,可以通过move把ptr1的独占权转移到ptr2上,此时ptr1为空,ptr2拥有独占权。ptr2会在作用域范围结束后,自动释放,避免了手动释放如果遗忘的错误。也可以通过ptr2.reset()提前进行自动释放。
Rust安全指针:
fn main() {
let b = Box::new(5);
println!("b = {}", b);
}
分配在堆上,没有共享拥有单一的所有权,编译规则限制严格,并且不提供类似于C++的自定义删除器(custom deleter),允许用户指定如何释放资源,以确保绝对的安全,离开作用域自动释放。
2.共享安全指针
C++提供了共享指针,多个指针可以共享一个对象,最后一个共享指针离开作用域,资源才会被释放
int main() {
std::shared_ptr<int> ptr1 = std::make_shared<int>(20); // 创建 shared_ptr
std::shared_ptr<int> ptr2 = ptr1; // 引用同一个对象
std::cout << "Count: " << ptr1.use_count() << std::endl; // 输出引用计数 2
std::cout << *ptr1 << std::endl; // 输出 20
}
Rust提供了Arc<T>原子引用计数的共享指针,用于多线程环境的共享智能指针,内部通过原子操作管理引用计数,确保绝对安全。
use std::sync::Arc;
use std::thread;
fn main() {
let arc = Arc::new(10);
let arc_clone = Arc::clone(&arc);
let handle = thread::spawn(move || {
println!("arc_clone = {}", arc_clone);
});
handle.join().unwrap();
}
结尾
上面的对于C++/Rust两类指针的分析,其实很容易看出来。C++普通安全指针提供的指定释放资源和指定删除器,C++共享安全指针提供的计数器对于自由度的依赖依然是非常高的,对于多线程的变量竞争,循环引用,自定义规则,提供的外在API都有内存安全的风险,且它的安全指针限制级完全不够严格。这就会导致各种问题。
Rust通过严格的编译规则,所有权,生命周期,安全并发等概念锁死了内存的安全行为(比如以原子计数的模式确保计数的正确性),基本上不会出现C++所面临的风险。Rust的出现看来是必然的了。
往期精彩回顾