Wang's blog

十七、不安全操作

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");
}