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. 文档化:详细说明安全前提

官方资源

练习平台


📚 参考资料

相关推荐
AngusKit1 小时前
开源扫描器拼盘 vs AngusSecurity
安全·安全性测试·代码安全·漏洞检测·trivy·安全扫描·gitleaks
山东科恩光电1 小时前
安全触边在自动化设备中的应用与风险控制方案分析
安全
汇智信科1 小时前
企业综合信息门户设计:基于 CMS 扩展的全要素内容与知识生态方案
安全·汇智信科·hsim·fastclaw·汇智龙虾
网易易盾2 小时前
应用加固如何应对AI辅助逆向?从单点防护到持续对抗
人工智能·安全
sbjdhjd3 小时前
从兼容路由到任意方法调用:ThinkPHP 5.1.29 RCE 复现与调试复盘 | 06
经验分享·安全·网络安全·数据挖掘·开源·php·rce
华章酱3 小时前
Linux服务器快速排查有问题的ip请求
服务器·安全·线上问题·攻击排查
Leaderxin3 小时前
还在用 Electron?6 种跨平台桌面方案横评:Rust + Vue 把安装包从 224MB 干到 4.7MB
rust·vue·跨平台·tauri·桌面应用
海盗12343 小时前
微软技术日报 2026-09-14:Rust 升为微软一级语言,云业务重组为智能体与基础设施
开发语言·microsoft·rust
传奇开心果编程3 小时前
【xilem0.4基础语法学与练】第53课 to_do_mvc 官方示例代码深度解析
学习·rust·前端框架