MST

星途 面试题库

面试题:Rust中unsafe代码块的安全边界基础认知

在Rust中,unsafe代码块存在安全风险,请阐述在哪些常见情况下需要使用unsafe代码块?并且说明unsafe代码块可能破坏哪些安全原则?
42.2万 热度难度
编程语言Rust

知识考点

AI 面试

面试题答案

一键面试

常见使用unsafe代码块的情况

  1. 直接操作内存
    • 例如使用raw pointer(裸指针)。Rust 通常通过智能指针(如BoxRc等)来管理内存,这些指针遵循 Rust 的内存安全规则。但在某些场景下,如与 C 语言交互,可能需要使用裸指针。裸指针不遵循 Rust 的内存安全规则,所以需要在unsafe块中使用。比如:
    let mut num = 5;
    let raw_ptr: *mut i32 = &mut num as *mut i32;
    unsafe {
        *raw_ptr = 10;
    }
    
  2. 调用不安全的外部函数
    • 当调用来自其他语言(如 C 语言)编写的函数时,这些函数可能不遵循 Rust 的安全规则。例如调用 C 标准库函数strcpy,因为它可能导致缓冲区溢出等安全问题,所以在 Rust 中调用这类函数需要unsafe块。在 Rust 中可以通过extern "C"块来声明外部函数,然后在unsafe块中调用,如下:
    extern "C" {
        fn strcpy(dest: *mut i8, src: *const i8) -> *mut i8;
    }
    fn main() {
        let mut dest = [0; 10];
        let src = "hello".as_ptr();
        unsafe {
            strcpy(dest.as_mut_ptr(), src);
        }
    }
    
  3. 访问或修改可变静态变量
    • 静态变量在整个程序生命周期内存在,并且可变静态变量可能会被多个线程同时访问和修改,这可能导致数据竞争。由于 Rust 无法在编译时保证对可变静态变量访问的安全性,所以需要unsafe块。例如:
    static mut COUNTER: i32 = 0;
    fn increment() {
        unsafe {
            COUNTER += 1;
        }
    }
    
  4. 实现unsafe trait
    • Rust 中有一些unsafe trait,如SyncSend。如果手动实现这些unsafe trait,需要在unsafe块中进行。例如,如果有一个自定义类型,其内部包含裸指针,并且希望该类型实现Send trait,就需要在unsafe块中实现:
    struct MyType {
        ptr: *mut i32
    }
    unsafe impl Send for MyType {}
    

unsafe代码块可能破坏的安全原则

  1. 内存安全
    • 悬空指针:在unsafe块中使用裸指针时,如果指向的内存被释放,但指针没有更新,就会产生悬空指针。例如,在unsafe块中手动释放内存后又使用指向该内存的指针:
    let mut num = Box::new(5);
    let raw_ptr: *mut i32 = &mut *num as *mut i32;
    drop(num);
    unsafe {
        let _ = *raw_ptr; // 这里会产生悬空指针,访问已释放的内存
    }
    
    • 缓冲区溢出:在unsafe块中进行手动内存操作时,如果没有正确检查边界,就可能导致缓冲区溢出。比如在手动分配和填充数组时:
    let mut buffer = vec![0; 5];
    let raw_ptr: *mut i32 = buffer.as_mut_ptr();
    unsafe {
        for i in 0..10 {
            *raw_ptr.offset(i as isize) = i; // 这里会导致缓冲区溢出
        }
    }
    
  2. 线程安全
    • 数据竞争:如前面提到的访问或修改可变静态变量,如果在多线程环境下,没有适当的同步机制,在unsafe块中对可变静态变量的访问和修改可能导致数据竞争。多个线程同时读写同一个可变静态变量,可能会导致未定义行为。例如:
    use std::thread;
    static mut COUNTER: i32 = 0;
    fn increment() {
        unsafe {
            COUNTER += 1;
        }
    }
    fn main() {
        let mut handles = vec![];
        for _ in 0..10 {
            let handle = thread::spawn(increment);
            handles.push(handle);
        }
        for handle in handles {
            handle.join().unwrap();
        }
        println!("Final counter value: {}", unsafe { COUNTER });
    }
    
    这里由于没有同步机制,多个线程同时修改COUNTER可能导致数据竞争。