十七、不安全操作
Published on
Rust编译器的静态检查可以保证代码是非常安全的,但是它过于严格,拒绝了一些可能有效的代码。因此如果确定需要使用这些代码,则要使用不安全操作告诉编译器放松条件。此外,如果要执行一些底层系统级操作,则必须要使用不安全操作。
解引裸指针
引用是由编译器保证总是有效的,因此可以安全地使用。在不安全的Rust中,有两种新的裸指针类型:*const T与*mut T。使用裸指针在放弃了安全性保证的同时,可以得到极大的性能提升或与其它语言/硬件进行交互的能力。与引用和智能指针不同,裸指针有以下特点:
- 允许忽略借用规则,同时可以有任意个指针指向同一地址
- 不保证裸指针指向有效数据
- 可以为空
- 不实现自动清理
以下代码从引用建立裸指针,可知指向的数据是有效的:
let mut num = 5;
// 建立不可变指针
let r1 = &num as *const i32;
// 建立可变指针
let r2 = &mut num as *mut i32;
以下代码从任意内存位置建立指针,不能保证指向有效数据:
let address = 0x012345usize;
let r = address as *const i32;
建立裸指针本身是安全的,但是由于指向的数据不一定有效,所以解引裸指针是不安全的:
unsafe {
println!("r1 is: {}", *r1);
println!("r2 is: {}", *r2);
}
调用不安全函数或方法
在函数声明前添加unsafe关键字声明不安全函数,这意味着调用者需要自行保证调用的正确性。调用此类函数需要在unsafe块中进行。
// 声明unsafe函数
unsafe fn dangerous() {}
// 调用unsafe函数
unsafe {
dangerous();
}
可以将不安全代码封装在安全函数中,无需将整个函数标记为不安全。
fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = values.len();
let ptr = values.as_mut_ptr();
assert!(mid <= len);
unsafe {
(
slice::from_raw_parts_mut(ptr, mid),
slice::from_raw_parts_mut(ptr.add(mid), len - mid),
)
}
}
使用extern函数调用外部代码:
extern "C" {
fn abs(input: i32) -> i32;
}
unsafe {
println!("Absolute value of -3 according to C: {}", abs(-3));
}
访问或修改可变静态变量
如果静态变量是不可变的,则其与常量类似,是安全的。但是有时需要使用可变静态变量,此时由于可能存在多线程操作,因此对其访问与修改都是不安全的。
static mut COUNTER: u32 = 0;
// 读写可变静态变量均不安全
unsafe {
COUNTER += inc;
}
unsafe {
println!("COUNTER: {}", COUNTER);
}
实现不安全特性
如果一个特性至少包含一个编译器无法检查的方法,则其为不安全特性。
// 声明不安全特性
unsafe trait Foo {
}
// 实现不安全特性
unsafe impl Foo for i32 {
}
访问union的字段
union一般作为接口用于访问C语言的union,由于Rust无法确定运行时union中字段的数据类型,因此是不安全的。
内联汇编
在Rust中调用汇编是不安全的:
unsafe {
asm!("nop");
}