11-Rust 不安全编程(unsafe 关键字 + 裸指针 + FFI + 内联汇编 + 内存安全保证)

摘要:本文深入讲解 Rust 不安全编程核心知识,涵盖 unsafe 关键字使用场景、裸指针操作、外部函数接口(FFI)、内联汇编、内存安全保证、unsafe 代码审查等核心内容。每个知识点配有完整代码示例、对比表格、实战场景及常见问题解答,帮助开发者在必要时安全地使用 unsafe 代码。

关键词:Rust、unsafe、不安全编程、裸指针、FFI、内联汇编、内存安全、外部函数接口

适合人群:已掌握 Rust 基础的开发者、需要与 C 代码交互的程序员、想深入理解内存模型的开发者

阅读时间:约 55 分钟

版本信息:Rust 1.70+ | 兼容 Windows/macOS/Linux


📖 目录导航

文章目录

  • [📖 目录导航](#📖 目录导航)
  • [一、unsafe 关键字](#一、unsafe 关键字)
    • [1.1 unsafe 使用场景](#1.1 unsafe 使用场景)
    • [1.2 unsafe 函数](#1.2 unsafe 函数)
    • [1.3 unsafe trait](#1.3 unsafe trait)
  • 二、裸指针操作
    • [2.1 裸指针基础](#2.1 裸指针基础)
    • [2.2 指针算术](#2.2 指针算术)
    • [2.3 从指针创建引用](#2.3 从指针创建引用)
  • [三、外部函数接口 FFI](#三、外部函数接口 FFI)
    • [3.1 调用 C 代码](#3.1 调用 C 代码)
    • [3.2 被 C 调用](#3.2 被 C 调用)
    • [3.3 字符串 FFI](#3.3 字符串 FFI)
  • 四、内联汇编
    • [4.1 基本语法](#4.1 基本语法)
    • [4.2 输入输出](#4.2 输入输出)
    • [4.3 架构特定汇编](#4.3 架构特定汇编)
  • 五、内存安全保证
    • [5.1 内存安全规则](#5.1 内存安全规则)
    • [5.2 安全封装](#5.2 安全封装)
  • [六、unsafe 代码审查](#六、unsafe 代码审查)
    • [6.1 审查清单](#6.1 审查清单)
    • [6.2 文档化](#6.2 文档化)
  • [💡 综合实战案例](#💡 综合实战案例)
    • [实战:安全封装 C 库](#实战:安全封装 C 库)
  • [❓ 常见问题 FAQ](#❓ 常见问题 FAQ)
  • [📝 学习资源与建议](#📝 学习资源与建议)
  • [📚 参考资料](#📚 参考资料)

一、unsafe 关键字

1.1 unsafe 使用场景

  Rust 的 unsafe 关键字允许绕过编译器的安全检查,仅在必要时使用。

unsafe 允许的操作:

操作 说明 示例
解引用裸指针 *const T、*mut T *ptr
调用 unsafe 函数 标记为 unsafe 的函数 unsafe { foo() }
访问或修改可变静态变量 static mut unsafe { STATIC_VAR = 1 }
实现 unsafe trait 标记为 unsafe 的 trait unsafe impl Send for Foo
访问 union 字段 union 类型 union.field

1.2 unsafe 函数

rust 复制代码
unsafe fn dangerous() {
    println!("This is an unsafe function");
}

fn main() {
    // 必须在 unsafe 块中调用
    unsafe {
        dangerous();
    }
}

1.3 unsafe trait

rust 复制代码
unsafe trait MyUnsafeTrait {
    fn do_something(&self);
}

struct MyStruct;

unsafe impl MyUnsafeTrait for MyStruct {
    fn do_something(&self) {
        println!("Doing something unsafe");
    }
}

unsafe 使用原则:

原则 说明 示例
最小化 unsafe 仅在必要时使用 封装 unsafe 代码
文档化不变量 说明安全前提 /// # Safety
测试边界条件 验证安全性 单元测试
审查代码 仔细审查 unsafe 块 Code Review

二、裸指针操作

2.1 裸指针基础

  裸指针不进行安全检查,需要手动管理。

rust 复制代码
fn main() {
    let mut num = 5;

    // 创建裸指针
    let r1 = &num as *const i32;
    let r2 = &mut num as *mut i32;

    // 解引用(需要 unsafe)
    unsafe {
        println!("r1 is: {}", *r1);
        println!("r2 is: {}", *r2);
        
        // 修改值
        *r2 = 10;
        println!("num is now: {}", num);
    }
}

裸指针类型对比:

类型 说明 可变性 示例
*const T 不可变裸指针 不可变 let ptr = &x as *const i32
*mut T 可变裸指针 可变 let ptr = &mut x as *mut i32

2.2 指针算术

rust 复制代码
fn main() {
    let mut v = vec![1, 2, 3, 4, 5];
    let ptr = v.as_mut_ptr();

    unsafe {
        // 指针算术
        let second = ptr.add(1);
        println!("Second element: {}", *second);

        // 修改值
        *second = 20;
        println!("Vector: {:?}", v);
    }
}

指针操作对比:

操作 方法 说明
偏移 ptr.add(offset) 向前偏移
偏移 ptr.sub(offset) 向后偏移
差值 ptr1.offset_from(ptr2) 指针差值
比较 ptr1 == ptr2 地址比较
对齐 ptr.align_offset(align) 对齐偏移

2.3 从指针创建引用

rust 复制代码
fn main() {
    let mut x = 42;
    let ptr = &mut x as *mut i32;

    unsafe {
        // 从裸指针创建引用
        let reference: &mut i32 = &mut *ptr;
        *reference = 100;
    }

    println!("x = {}", x);
}

安全注意事项:

注意事项 说明 风险
空指针 检查是否为 null 解引用 null 导致 UB
悬垂指针 指向已释放内存 访问无效内存
数据竞争 多线程访问 未定义行为
对齐 指针必须对齐 未对齐访问 UB

三、外部函数接口 FFI

3.1 调用 C 代码

  Rust 可以通过 FFI 调用 C 函数。

rust 复制代码
// 声明外部 C 函数
extern "C" {
    fn abs(input: i32) -> i32;
    fn printf(format: *const i8, ...) -> i32;
}

fn main() {
    unsafe {
        println!("Absolute value: {}", abs(-5));
    }
}

3.2 被 C 调用

rust 复制代码
// 导出 C 可调用的函数
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
    a + b
}

FFI 类型映射:

Rust 类型 C 类型 说明
c_char char 字符
c_int int 整数
c_float float 浮点数
c_double double 双精度
c_void void 空指针

3.3 字符串 FFI

rust 复制代码
use std::ffi::{CStr, CString};
use std::os::raw::c_char;

extern "C" {
    fn strlen(s: *const c_char) -> usize;
}

fn main() {
    // Rust -> C 字符串
    let c_string = CString::new("Hello from Rust").unwrap();
    
    unsafe {
        let len = strlen(c_string.as_ptr());
        println!("String length: {}", len);
    }

    // C -> Rust 字符串
    let c_str = CStr::from_bytes_with_nul(b"Hello\0").unwrap();
    let rust_str = c_str.to_str().unwrap();
    println!("Rust string: {}", rust_str);
}

字符串转换对比:

转换 方法 说明
Rust -> C CString::new() 创建 C 字符串
C -> Rust CStr::from_ptr() 从指针创建
字节转换 to_bytes_with_nul() 包含 null 终止符
字符串转换 to_str() 转为 Rust &str

四、内联汇编

4.1 基本语法

  Rust 1.59+ 支持稳定的内联汇编。

rust 复制代码
fn main() {
    let x: u64;
    unsafe {
        std::arch::asm!(
            "mov {}, 5",
            out(reg) x,
        );
    }
    println!("x = {}", x);
}

4.2 输入输出

rust 复制代码
fn main() {
    let a: u64 = 10;
    let b: u64 = 20;
    let result: u64;

    unsafe {
        std::arch::asm!(
            "add {0}, {1}",
            inout(reg) a => result,
            in(reg) b,
        );
    }
    println!("{} + {} = {}", a, b, result);
}

汇编操作数对比:

操作数 说明 示例
in(reg) 输入 in(reg) value
out(reg) 输出 out(reg) value
inout(reg) 输入输出 inout(reg) value
lateout(reg) 延迟输出 lateout(reg) value
const 常量 const 5
sym 符号 sym function_name

4.3 架构特定汇编

rust 复制代码
#[cfg(target_arch = "x86_64")]
fn cpuid() -> (u32, u32, u32, u32) {
    let eax;
    let ebx;
    let ecx;
    let edx;

    unsafe {
        std::arch::asm!(
            "cpuid",
            in("eax") 0,
            in("ecx") 0,
            out("eax") eax,
            out("ebx") ebx,
            out("ecx") ecx,
            out("edx") edx,
        );
    }

    (eax, ebx, ecx, edx)
}

五、内存安全保证

5.1 内存安全规则

  即使使用 unsafe,也必须保证内存安全。

内存安全规则:

规则 说明 违反后果
不悬垂 指针必须指向有效内存 UB
不空指针 解引用前检查 null 崩溃
不数据竞争 同一时间只有一个可变引用 UB
对齐 指针必须正确对齐 UB
初始化 必须初始化后读取 UB

5.2 安全封装

rust 复制代码
struct MyVec {
    ptr: *mut i32,
    len: usize,
    cap: usize,
}

impl MyVec {
    fn new() -> Self {
        Self {
            ptr: std::ptr::null_mut(),
            len: 0,
            cap: 0,
        }
    }

    // 安全接口
    fn push(&mut self, value: i32) {
        if self.len == self.cap {
            self.grow();
        }
        unsafe {
            *self.ptr.add(self.len) = value;
        }
        self.len += 1;
    }

    fn grow(&mut self) {
        // 内部 unsafe 实现
        // 对外提供安全接口
    }
}

封装原则:

原则 说明 示例
安全接口 对外提供安全 API fn push()
内部 unsafe 内部使用 unsafe unsafe { *ptr = value }
不变量检查 验证安全前提 assert!(!ptr.is_null())
文档化 说明安全要求 /// # Safety

六、unsafe 代码审查

6.1 审查清单

检查项 说明 示例
指针有效性 指针指向有效内存 !ptr.is_null()
生命周期 引用生命周期正确 不返回悬垂引用
别名规则 遵守借用规则 不创建多个可变引用
对齐 指针正确对齐 ptr.align_offset()
初始化 内存已初始化 不读取未初始化值

6.2 文档化

rust 复制代码
/// # Safety
///
/// This function is unsafe because:
/// - `ptr` must be a valid pointer
/// - `ptr` must be properly aligned
/// - `ptr` must point to initialized memory
/// - The memory must not be accessed elsewhere
unsafe fn process_data(ptr: *mut i32, len: usize) {
    // 实现
}

💡 综合实战案例

实战:安全封装 C 库

rust 复制代码
use std::ffi::{CStr, CString};
use std::os::raw::c_char;

// 外部 C 函数声明
extern "C" {
    fn strdup(s: *const c_char) -> *mut c_char;
    fn free(ptr: *mut c_void);
}

use std::os::raw::c_void;

/// 安全封装 C 字符串
pub struct SafeCString {
    ptr: *mut c_char,
}

impl SafeCString {
    /// 从 Rust 字符串创建
    pub fn new(s: &str) -> Result<Self, std::ffi::NulError> {
        let c_string = CString::new(s)?;
        let ptr = unsafe { strdup(c_string.as_ptr()) };
        Ok(Self { ptr })
    }

    /// 转为 Rust 字符串
    pub fn to_str(&self) -> Result<&str, std::str::Utf8Error> {
        unsafe {
            let c_str = CStr::from_ptr(self.ptr);
            c_str.to_str()
        }
    }
}

impl Drop for SafeCString {
    fn drop(&mut self) {
        unsafe {
            free(self.ptr as *mut c_void);
        }
    }
}

fn main() {
    let s = SafeCString::new("Hello, FFI!").unwrap();
    println!("{}", s.to_str().unwrap());
}

项目知识点:

  • FFI 调用 C 函数
  • CString 转换
  • 安全封装 unsafe 代码
  • Drop 自动释放内存

❓ 常见问题 FAQ

Q1:什么时候应该使用 unsafe?

  A:以下场景可以考虑使用 unsafe:

  1. 与 C 代码交互(FFI)
  2. 实现高性能数据结构
  3. 访问硬件或系统调用
  4. 实现底层抽象(智能指针、集合)

Q2:unsafe 代码一定不安全吗?

  A:不是。unsafe 只是绕过编译器检查,开发者必须手动保证安全。正确编写的 unsafe 代码可以是安全的。

Q3:如何调试 unsafe 代码?

  A:调试技巧:

  1. 使用 Miri 检测 UB:cargo +nightly miri run
  2. 使用 AddressSanitizer:RUSTFLAGS="-Z sanitizer=address"
  3. 编写单元测试验证安全性
  4. 仔细审查不变量

Q4:FFI 字符串转换有什么注意事项?

  A:注意事项:

  1. C 字符串以 null 结尾
  2. Rust 字符串可能包含 null
  3. 使用 CString 转换 Rust -> C
  4. 使用 CStr 转换 C -> Rust
  5. 注意内存释放

Q5:如何保证 unsafe 代码的正确性?

  A:保证正确性方法:

  1. 编写详细的 /// # Safety 文档
  2. 使用 Miri 检测 UB
  3. 编写全面的测试
  4. Code Review 审查
  5. 最小化 unsafe 范围

📝 学习资源与建议

学习建议

  1. 尽量避免 unsafe :优先使用安全抽象

  2. 理解内存模型 :深入理解指针和内存布局

  3. 使用 Miri :检测未定义行为

  4. 封装 unsafe :对外提供安全接口

  5. 文档化:详细说明安全前提

官方资源

练习平台


📚 参考资料

相关推荐
hasty4 小时前
2026-10-04_ABSENTIA鉴权不变量审计_CSDN_待发布
安全
杭州领祺科技6 小时前
27 号令横向隔离落地清单:储能边缘计算网关 ≠ 电力专用网闸,算电协同中心安全分区/纵向加密/SPDnet 怎么配
人工智能·安全·网络安全·边缘计算·储能·电力·电力监控
jimmyleeee8 小时前
大模型安全之四十:纵深防御----AI安全没有银弹
人工智能·安全
水饺编程9 小时前
第1章:下载 Linux 0.12 内核
linux·c语言·汇编·ubuntu
水饺编程9 小时前
第1章:开发环境搭建,在 Windows 中安装 Git
linux·c语言·汇编·git·ubuntu
迪康coolmu9 小时前
屏幕水印与防拍照落地:从水印类型到泄密溯源的完整配置
运维·网络·安全·网络安全·数据安全
网络毒刘11 小时前
AtomGit Actions + AI 评审草稿:PR 描述自动生成与安全敏感词门禁示例
人工智能·安全
恒拓高科WorkPlus11 小时前
BeeWorks 企业即时通讯平台 - 常见问题(FAQ)
安全
奈落2411 小时前
【RAG 深度修炼】专栏 · 第 9 期(收官):安全专题与生产 Checklist 总集——从能跑的 Demo 到睡得着觉的生产系统
大数据·网络·人工智能·安全·ai编程