---
title: 裸指针与 FFI
url: https://doc.liz6.com/rust/07-unsafe-rust/01-raw-pointers-and-ffi
locale: zh
area: rust
tags:
- rust
- Unsafe Rust
date: 2026-06-30
modified: 2026-06-30
description: const T 和 mut T 不受借用检查器约束——可以随意别名、可以不指向合法内存。它们存在的唯一理由是 FFI:调用 C 库、接收 C 的回调、操作硬件寄存器。unsafe 块不是"禁用安全检查",而是把安全责任从编译器转移到程序员。
---

# 裸指针与 FFI

> `*const T` 和 `*mut T` 不受借用检查器约束——可以随意别名、可以不指向合法内存。它们存在的唯一理由是 FFI:调用 C 库、接收 C 的回调、操作硬件寄存器。unsafe 块不是"禁用安全检查",而是把安全责任从编译器转移到程序员。

## 裸指针: 没有 borrow checker 的指针

```rust
let x = 42;
let ptr: *const i32 = &x;            // 引用 → 裸指针 (安全)
unsafe {
    println!("{}", *ptr);            // 解引用 (unsafe — 编译器不验证有效性)
}
```

裸指针不需要遵守 borrow checker 规则——你可以有多个 `*mut T` 指向同一数据、可以是 null、可以不保证 alignment。所有 responsibility 转移到程序员。

与 C `int *` 的区别：Rust 区分 `*const T` 和 `*mut T`（类似 C 的 `const int *` vs `int *`）。但 C 可以隐式丢弃 const，Rust 不行。

## extern "C": C 函数接口

```rust
extern "C" {
    fn abs(input: i32) -> i32;       // 声明 C 函数
}

#[no_mangle]                         // 禁止 Rust name mangling
pub extern "C" fn rust_callback(x: i32) -> i32 {
    x + 1                            // C 代码可以调用这个函数
}
```

`extern "C"` 使用 C calling convention：参数在寄存器/栈中按目标平台的 C ABI 排列。x86-64 上这是 System V AMD64 ABI（rdi, rsi, rdx, rcx, r8, r9 传参）。也可以使用 `extern "system"` (Windows API) 和 `extern "Rust"` (默认)。

## #[repr(C)]: C 兼容的内存布局

```rust
#[repr(C)]
struct MyStruct {
    a: i32,                          // offset 0
    // padding: 4 bytes               // C 要求 f64 对齐到 8
    b: f64,                          // offset 8
}                                    // size: 16 bytes
```

没有 `#[repr(C)]` 时，Rust 编译器可以重排字段顺序以优化 struct 大小。加上 `#[repr(C)]` 后，字段按声明顺序，按 C ABI 要求 padding——保证与 C struct 二进制兼容。`#[repr(C, packed)]` 消除所有 padding（但可能产生未对齐引用——unsafe）。

## bindgen: 自动生成 Rust FFI 绑定

从 C header 生成 Rust 的 `extern` 声明、struct 定义和常量：

```rust
// build.rs:
fn main() {
    println!("cargo:rerun-if-changed=wrapper.h");
    let bindings = bindgen::Builder::default()
        .header("wrapper.h")
        .allowlist_function("my_lib_.*")   // 只 binding 这些函数
        .generate().expect("bindgen failed");
    bindings.write_to_file("src/bindings.rs").unwrap();
}
// src/lib.rs:
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
```

## Callback: C 调 Rust 的陷阱

```rust
extern "C" fn callback(x: i32) {
    println!("C called with {}", x);
}
unsafe { register_callback(callback as unsafe extern "C" fn(i32)); }
```

Rust 函数作为 callback 传给 C 时，**panic 不能跨越 FFI 边界**——如果 callback 中 panic，且 unwind 穿过 C 栈帧，这是 UB（C 栈上没有 unwind tables）。解决方案：在 callback 中用 `std::panic::catch_unwind` 捕获 panic，或在 Cargo.toml 中设 `panic = "abort"`。

## 参考

- **Rustonomicon**: FFI, C ABI
- **bindgen**: rust-lang.github.io/rust-bindgen
- **The Embedded Rust Book**: FFI and C interaction

*Keywords: raw pointer, *const, *mut, extern "C", #[no_mangle], repr(C), bindgen, FFI callback, C ABI*
