rust unsafe

Created

2024-05-27 08:18:24

Updated

2024-10-22 12:55:02

1 为什么需要以及它的作用

为什么需要?
  • Rust 是一种系统编程语言, 强调内存安全、数据竞争自由和并发性. 尽管 Rust 提供了许多机制来保证内存安全, 但有时程序员需要执行一些操作,这些操作无法通过 Rust 的安全抽象来表示, 或者执行起来效率较低.
  • 在这种情况下, Rust 提供了 unsafe 块或 unsafe 函数
  • unsafe 代码表明 可能有不安全的操作, 因为里面编写的代码 ,一些检查可能是不执行的, 需要程序员自己注意代码的编写, 写的没问题 ,那实际上就是安全的, 换句话说如果没写unsafe 编译器会报错, 是为了提醒你 这块代码 可能有不安全的操作, 需要你注意代码的编写
  • 根据上面的说法, 显然 unsafe代码块里的 代码 本来会做的检查还是会做
Diagram

2 裸指针

Diagram
fn main() {
    struct Two {
        first: i8,
        second: i8,
    }
    let mut num = 777i32;
    // 0000 0011  0000 1001
    println!("num的二进制表示: {:b}", num);
    // 先将地址 &num 转换为 指针 *const i32 ,
    // 然后将它转换为 *const i8, 表示现在是以一个字节的方式读取这块内存地址
    let r1 = &num as *const i32 as *const i8;
    // 步进1后的指针, 这里就表示该指针指向 r1 所在地址下一个字节的内存
    // 这些操作无需 unsafe 包裹
    let f = r1.wrapping_add(1);
    // 转换为结构体的方式来读取这块内存
    let r2 = &num as *const i32 as *const Two;
    // 转换为以数组的方式来读写这块内存
    let r3 = &mut num as *mut i32 as *mut [i8; 2];
    // 在解引用操作的时候,才需要unsafe包裹
    unsafe {
        println!("num第一个字节==> r1 : {}", *r1); // 9
        println!("num第二个字节==> r1 : {}", *f);  // 3
        println!("num第一个字节==> r2 : {:?}", (*r2).first); // 9
        println!("num第二个字节==> r2 : {}", (*r2).second);  // 3
        println!("转换为数组 r3,num第一个字节:{}-num第二个字节:{}", (*r3)[0], (*r3)[1]);
        (*r3)[0] = 11; // 将第一个字节修改为11
        println!("{num}");
    }

}
fn main() {
    let b;
    {
        let a = "hello".to_string();
        b = &a as *const String;
    } // 编译器没有报错, 说明忽略了借用检查
    let c = b.wrapping_add(1);
    unsafe {
        let d = b as *const [u64; 3];
        println!("{}-0x{:x}-{}", (*d)[0], (*d)[1], (*d)[2])
    }
    // 可以指向不合理的内存地址, 这里也说明了, 它不会像智能指针那样自动清理, 如果清理了, 那还得了
    let address = 0x012345usize;
    let r = address as *const i32;

    // 空指针, 不指向任何有效的i32值
    let p = std::ptr::null::<i32>();
    println!("p 是否为空指针 {}", p.is_null());
}
fn main() {
    let f;
    {
        let b = Box::new(5);
        println!("{:p}", b); // 堆上的5的地址
        println!("{:p}", &b); // 栈上b变量的地址
        // 将 b 转换为 *mut T 可变裸指针
        // c 的类型是 这里是 *mut i32
        // b 被move 掉了,但是这个c指向了 b指向的内存,且c没有这块内存的所有权
        let c = Box::into_raw(b);
        println!("{:p}", c); // 堆上5的地址
        unsafe {
            println!("{}", *c);
            *c = 23;
            f = &*c;
            // Box::from_raw 重新获取所有权,这样离开作用域后, 回收了
            // 取消注释后, *f 的值就不对了, 因为堆上的数据5被回收了
            // let s = Box::from_raw(c);
        }
    }// 由于没有 变量对 堆上的数据5  拥有所有权, 所以没 回收
    println!("f=={}", *f);
}
需要 cargo add libc
fn main() {
    // 使用 libc 库中的 malloc 函数分配内存
    let raw_ptr = unsafe { libc::malloc(std::mem::size_of::<i32>() as libc::size_t) as *mut i32 };
    println!(
        "raw_ptr在栈上分配的地址: {:p}\nraw_ptr申请的堆地址: {:p}",
        &raw_ptr,
        unsafe { &*raw_ptr }
    );
    let b = Box::new(11);
    println!("b指向的堆地址: {:p}", b);
    if raw_ptr.is_null() {
        eprintln!("Memory allocation failed");
        return;
    }

    // 安全地使用裸指针
    unsafe {
        // 初始化内存
        *raw_ptr = 42;

        // 打印内存中的值
        println!("Value at raw pointer: {}", *raw_ptr);
    }

    // 注意:这里没有释放内存,这将导致内存泄漏
    // 正确的做法应该是调用 libc::free 来释放内存
    unsafe {
        libc::free(raw_ptr as *mut libc::c_void);
        println!("free 后 raw_ptr指向的值: {}", *raw_ptr); // 0
    }
}

3 修改可变静态变量

可变的静态变量,修改需要unsafe
static mut COUNTER: u32 = 0;
fn add_to_count(inc: u32) {
    unsafe {
        COUNTER += inc;
    }
}

fn main() {
    add_to_count(3);

    println!("COUNTER: {}", unsafe { COUNTER });
}

4 unsafe fn

Tip

unsafe函数或方法: 就是在定义前面加上unsafe关键字.

unsafe fn dangerous() {}
fn main() {
    // 需要unsafe
    unsafe {
        dangerous();
    }
}
调用内部实现有unsafe代码块的函数或方法,不需要unsafe包裹
fn main() {
    let mut v = vec![1, 2, 3, 4, 5, 6];

    let r = &mut v[..];
    // 切分成2个切片 [0,mid) [mid,len)
    // split_at_mut 方法里面有 unsafe 代码块, 但是我们这里调用的时候 不需要unsafe
    let (a, b) = r.split_at_mut(3);

    assert_eq!(a, &mut [1, 2, 3]);
    assert_eq!(b, &mut [4, 5, 6]);
}
split_at_mut源码
use std::slice;

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),
        )
    }
}
直接使用std::slice库
use std::slice;
fn main() {
    let mut a = [1, 2, 3, 4, 5];
    let b = &mut a;
    let r = b as *mut i32; // a.as_mut_ptr()
    let values: &[i32] = unsafe { slice::from_raw_parts_mut(r, 2) };
    println!("{:?}", values);
}

5 unsafe trait, unsafe impl

unsafe trait Foo {
    // methods go here
}

unsafe impl Foo for i32 {
    // method implementations go here
}

fn main() {}

6 extern 调用外部函数(FFI)

Tip
  • FFI: Foreign Function Interface 外部函数接口, 可以让一种编程语言定义函数,然后其他语言可以调用

6.1 rust 调用C

extern "C" {
    // 我们想要调用的外部函数的名称和参数返回值定义
    fn abs(input: i32) -> i32;
}

fn main() {
    unsafe {
        println!("Absolute value of -3 according to C: {}", abs(-3));
    }
}

6.2 c语言调用rust

cargo new ccc
tree
.
├── Cargo.lock
├── Cargo.toml
├── main.c
├── src
│   └── lib.rs
[package]
name = "ccc"
version = "0.1.0"
edition = "2021"

[dependencies]
[lib]
crate-type = ["cdylib"]
#[no_mangle]
pub extern "C" fn call_from_c() {
    println!("Just called a Rust function from C!");
}
#include <stdio.h>

// 声明 Rust 函数
extern int call_from_c();

int main() {
    call_from_c();
    return 0;
}
# 编译rust lib, 会在 target/release目录下生成 libccc.so
# 不同的操作系统 生成的不太一样.
cargo build --release
# 编译 main.c, -lccc 中的ccc 是rust lib package name
gcc -o main main.c -L target/release -lccc -Wl,-rpath,target/release
# 运行编译后的c程序
./main
#[no_mangle] 关于名称混淆(Name Mangling)
  • 名称混淆是编译器的一种行为,它会根据函数的参数类型、返回类型、函数名、命名空间等信息生成一个唯一的名称
  • 这个过程确保即使在同一个程序中存在多个同名但参数类型不同的函数时,编译后的二进制文件中也不会出现命名冲突
  • #[no_mangle] 的作用
    • 当你使用 #[no_mangle] 属性时,Rust 编译器会保留函数的原始名称,而不是生成一个混淆后的名称
    • 当你需要从其他语言(如 C 或 C++)调用 Rust 函数时, 才可以通过这个函数名找到对应的函数
    • 所以你在rust中定义的rust函数必须全局唯一的函数名

7 禁用 unsafe

在文件顶部写上
#![forbid(unsafe_code)]
fn main() {
    // 执行 程序, 会报错, 提示你不能用unsafe{}
    unsafe{

    }
}

8 内置函数

Back to top