Rust - Box::into_raw、Box::from_raw 与 std::mem

文章目录[x]
  1. 1:概述
  2. 2:核心概念
  3. 2.1:Box 智能指针
  4. 2.2:从自动到手动的转换
  5. 3:Box::into_raw 详解
  6. 3.1:函数签名和行为
  7. 3.2:内存布局保证
  8. 4:Box::from_raw 详解
  9. 4.1:函数签名和行为
  10. 4.2:安全要求
  11. 5:与 std::mem 模块的关系
  12. 5.1:std::mem::forget vs Box::into_raw
  13. 5.2:std::mem::ManuallyDrop 的协作
  14. 5.3:std::mem::transmute 的危险组合
  15. 6:实际应用场景
  16. 6.1:场景1:FFI(外部函数接口)
  17. 6.2:场景2:Android图像处理缓冲区管理(JNI)
  18. 7:性能考虑
  19. 7.1:内存分配开销
  20. 7.2:内存对齐和布局
  21. 8:调试和故障排除
  22. 8.1:内存泄露检测
  23. 9:最佳实践总结
  24. 9.1:1. 配对使用原则
  25. 9.2:2. 错误处理模式
  26. 9.3:3. 文档和注释
  27. 10:总结

概述

在 Rust 中,Box::into_rawBox::from_raw 是手动内存管理的核心工具,它们与 std::mem 模块的函数密切相关,共同构成了 Rust 精确控制内存生命周期的机制。本文深入探讨这些组件之间的关系、使用场景以及安全考虑。

核心概念

Box 智能指针

Box<T> 是 Rust 中最简单的智能指针,它在堆上分配内存并提供 RAII(资源获取即初始化)保证。

// 基本 Box 使用
let x = Box::new(42);
println!("{}", *x);
// x 在作用域结束时自动释放内存

从自动到手动的转换

当需要精确控制内存生命周期时,可以将自动管理的 Box 转换为手动管理的原始指针。

use std::mem;

// 自动管理 -> 手动管理
let boxed = Box::new(String::from("Hello, World!"));
let raw_ptr = Box::into_raw(boxed);  // 转移所有权,获得原始指针

// 手动管理 -> 自动管理
unsafe {
    let restored_box = Box::from_raw(raw_ptr);
    println!("{}", restored_box);
    // restored_box 在此处自动释放
}

Box::into_raw 详解

函数签名和行为

impl<T> Box<T> {
    pub fn into_raw(b: Box<T>) -> *mut T
}

Box::into_raw 的核心特性:

  1. 消费原 Box:函数获取 Box<T> 的所有权
  2. 返回原始指针:返回指向堆上数据的 *mut T
  3. 转移责任:内存管理责任从 Rust 转移给调用者
  4. 不运行析构函数:不会调用 TDrop 实现
use std::alloc::{alloc, dealloc, Layout};
use std::ptr;

struct Resource {
    name: String,
    id: u32,
}

impl Drop for Resource {
    fn drop(&mut self) {
        println!("释放资源: {} (id: {})", self.name, self.id);
    }
}

fn demonstrate_into_raw() {
    let resource = Resource {
        name: "重要资源".to_string(),
        id: 42,
    };

    let boxed = Box::new(resource);
    println!("Box 创建完成");

    // 转换为原始指针,不会触发 Drop
    let raw_ptr = Box::into_raw(boxed);
    println!("转换为原始指针: {:p}", raw_ptr);

    // 现在调用者负责内存管理
    unsafe {
        // 手动释放方法1:转换回 Box(推荐)
        let restored = Box::from_raw(raw_ptr);
        println!("资源名称: {}", restored.name);
        // restored 在此处自动 drop,打印释放信息
    }
}

内存布局保证

Box::into_raw 返回的指针具有以下保证:

use std::mem;

fn memory_layout_guarantees() {
    let data = vec![1, 2, 3, 4, 5];
    let boxed = Box::new(data);

    // 获取 Box 内部数据的地址
    let box_addr = &*boxed as *const Vec<i32> as usize;

    let raw_ptr = Box::into_raw(boxed);
    let raw_addr = raw_ptr as usize;

    // Box::into_raw 返回的指针指向相同的内存位置
    assert_eq!(box_addr, raw_addr);

    // 指针对齐保证
    assert_eq!(raw_addr % mem::align_of::<Vec<i32>>(), 0);

    unsafe {
        // 指针非空保证
        assert!(!raw_ptr.is_null());

        // 恢复 Box 进行清理
        let _restored = Box::from_raw(raw_ptr);
    }
}

Box::from_raw 详解

函数签名和行为

impl<T> Box<T> {
    pub unsafe fn from_raw(raw: *mut T) -> Box<T>
}

Box::from_raw 的关键特性:

  1. unsafe 函数:需要在 unsafe 块中调用
  2. 重建 Box:从原始指针重新构造 Box<T>
  3. 恢复 RAII:重新启用自动内存管理
  4. 严格要求:只能用于 Box::into_raw 产生的指针
use std::ptr;

fn demonstrate_from_raw() {
    let original_data = vec![1, 2, 3, 4, 5];
    let boxed = Box::new(original_data);

    // 记录原始信息用于验证
    let original_len = boxed.len();
    let raw_ptr = Box::into_raw(boxed);

    // 在原始指针存在期间,可以进行一些操作
    unsafe {
        // 只读访问
        let vec_ref = &*raw_ptr;
        println!("通过原始指针访问长度: {}", vec_ref.len());

        // 可变访问
        let vec_mut = &mut *raw_ptr;
        vec_mut.push(6);
        println!("修改后长度: {}", vec_mut.len());

        // 重建 Box
        let restored_box = Box::from_raw(raw_ptr);
        println!("恢复的 Box 长度: {}", restored_box.len());
        println!("数据: {:?}", *restored_box);
        // restored_box 在此处自动释放
    }
}

安全要求

Box::from_raw 有严格的安全要求:

use std::alloc::{alloc, Layout};

fn safety_requirements_demo() {
    // ✅ 正确用法:配对使用
    let boxed = Box::new(42i32);
    let raw_ptr = Box::into_raw(boxed);
    unsafe {
        let restored = Box::from_raw(raw_ptr);
        println!("值: {}", *restored);
    }

    // ❌ 错误用法示例(不要在实际代码中这样做)
    unsafe {
        // 错误1:使用非 Box::into_raw 产生的指针
        let layout = Layout::new::<i32>();
        let raw_memory = alloc(layout) as *mut i32;
        *raw_memory = 42;
        // let bad_box = Box::from_raw(raw_memory);  // 未定义行为!

        // 错误2:重复使用同一个指针
        let boxed = Box::new(42);
        let ptr = Box::into_raw(boxed);
        let _box1 = Box::from_raw(ptr);
        // let _box2 = Box::from_raw(ptr);  // 双重释放!

        // 清理手动分配的内存
        std::alloc::dealloc(raw_memory as *mut u8, layout);
    }
}

与 std::mem 模块的关系

std::mem::forget vs Box::into_raw

use std::mem;

fn forget_vs_into_raw() {
    // 方法1:使用 mem::forget(不推荐用于转移所有权)
    let boxed1 = Box::new(vec![1, 2, 3]);
    let ptr1 = &*boxed1 as *const Vec<i32>;
    mem::forget(boxed1);  // 防止析构,但没有获得管理权
    // ptr1 现在悬空,因为我们无法安全地重新获得所有权

    // 方法2:使用 Box::into_raw(推荐)
    let boxed2 = Box::new(vec![4, 5, 6]);
    let ptr2 = Box::into_raw(boxed2);  // 获得可管理的原始指针
    unsafe {
        // 可以安全地重新获得所有权
        let restored = Box::from_raw(ptr2);
        println!("恢复的数据: {:?}", *restored);
    }
}

std::mem::ManuallyDrop 的协作

use std::mem::ManuallyDrop;

struct ComplexResource {
    data: Vec<u8>,
    handle: u64,
}

impl Drop for ComplexResource {
    fn drop(&mut self) {
        println!("释放复杂资源,句柄: {}", self.handle);
    }
}

fn manually_drop_integration() {
    let resource = ComplexResource {
        data: vec![1, 2, 3, 4, 5],
        handle: 12345,
    };

    // 使用 ManuallyDrop 包装
    let manual_resource = ManuallyDrop::new(resource);

    // 创建 Box 并转换为原始指针
    let boxed = Box::new(manual_resource);
    let raw_ptr = Box::into_raw(boxed);

    unsafe {
        // 访问数据
        let resource_ref = &(*raw_ptr);
        println!("数据长度: {}", resource_ref.data.len());

        // 手动控制释放
        let mut restored_box = Box::from_raw(raw_ptr);

        // 手动释放 ManuallyDrop 包装的内容
        ManuallyDrop::drop(&mut *restored_box);

        // Box 本身会在作用域结束时释放,但内容已经被手动释放
    }
}

std::mem::transmute 的危险组合

use std::mem;

fn dangerous_transmute_patterns() {
    // ⚠️ 危险:不要将 Box::into_raw 与 transmute 随意组合
    let boxed = Box::new(42u32);
    let raw_ptr = Box::into_raw(boxed);

    unsafe {
        // ❌ 错误:类型不匹配的 transmute
        // let bad_ptr: *mut u64 = mem::transmute(raw_ptr);  // 危险!

        // ✅ 正确:类型安全的操作
        let value_ref = &*raw_ptr;
        println!("值: {}", *value_ref);

        // 正确恢复
        let restored = Box::from_raw(raw_ptr);
        println!("恢复的值: {}", *restored);
    }
}

实际应用场景

场景1:FFI(外部函数接口)

use std::ffi::{CString, c_char};
use std::ptr;

// 导出给 C 使用的函数
#[no_mangle]
pub extern "C" fn create_string_buffer(content: *const c_char) -> *mut String {
    if content.is_null() {
        return ptr::null_mut();
    }

    unsafe {
        let c_str = std::ffi::CStr::from_ptr(content);
        match c_str.to_str() {
            Ok(rust_str) => {
                let string = String::from(rust_str);
                Box::into_raw(Box::new(string))
            }
            Err(_) => ptr::null_mut(),
        }
    }
}

#[no_mangle]
pub extern "C" fn get_string_length(string_ptr: *const String) -> usize {
    if string_ptr.is_null() {
        return 0;
    }

    unsafe {
        (*string_ptr).len()
    }
}

#[no_mangle]
pub extern "C" fn destroy_string_buffer(string_ptr: *mut String) {
    if !string_ptr.is_null() {
        unsafe {
            // 重新获得所有权并自动释放
            let _boxed_string = Box::from_raw(string_ptr);
        }
    }
}

// 使用示例
fn ffi_usage_example() {
    let content = CString::new("Hello, FFI!").unwrap();
    let string_ptr = create_string_buffer(content.as_ptr());

    if !string_ptr.is_null() {
        let length = get_string_length(string_ptr);
        println!("字符串长度: {}", length);

        destroy_string_buffer(string_ptr);
    }
}

场景2:Android图像处理缓冲区管理(JNI)

use std::mem;
use jni::JNIEnv;
use jni::objects::{JClass, JByteArray};
use jni::sys::{jlong, jint, jbyteArray};

#[repr(C)]
struct ImageBuffer {
    width: u32,
    height: u32,
    channels: u32,
    data: Vec<u8>,
}

impl ImageBuffer {
    fn new(width: u32, height: u32, channels: u32) -> Self {
        let size = (width * height * channels) as usize;
        ImageBuffer {
            width,
            height,
            channels,
            data: vec![0; size],
        }
    }

    fn get_pixel(&self, x: u32, y: u32) -> Option<&[u8]> {
        if x < self.width && y < self.height {
            let start = ((y * self.width + x) * self.channels) as usize;
            let end = start + self.channels as usize;
            Some(&self.data[start..end])
        } else {
            None
        }
    }

    fn set_pixel(&mut self, x: u32, y: u32, pixel: &[u8]) -> bool {
        if x < self.width && y < self.height && pixel.len() == self.channels as usize {
            let start = ((y * self.width + x) * self.channels) as usize;
            self.data[start..start + pixel.len()].copy_from_slice(pixel);
            true
        } else {
            false
        }
    }
}

// Java 端接口
#[no_mangle]
pub extern "system" fn Java_ImageProcessor_createBuffer(
    _env: JNIEnv,
    _class: JClass,
    width: jint,
    height: jint,
    channels: jint,
) -> jlong {
    let buffer = ImageBuffer::new(width as u32, height as u32, channels as u32);
    Box::into_raw(Box::new(buffer)) as jlong
}

#[no_mangle]
pub extern "system" fn Java_ImageProcessor_destroyBuffer(
    _env: JNIEnv,
    _class: JClass,
    ptr: jlong,
) {
    if ptr != 0 {
        unsafe {
            let _buffer = Box::from_raw(ptr as *mut ImageBuffer);
            // 自动释放内存
        }
    }
}

#[no_mangle]
pub extern "system" fn Java_ImageProcessor_setPixel(
    _env: JNIEnv,
    _class: JClass,
    ptr: jlong,
    x: jint,
    y: jint,
    pixel_data: JByteArray,
) -> jint {
    if ptr == 0 {
        return -1;
    }

    unsafe {
        let buffer = &mut *(ptr as *mut ImageBuffer);

        // 获取 Java 字节数组
        let env = &_env;
        let pixel_bytes = match env.convert_byte_array(&pixel_data) {
            Ok(bytes) => bytes,
            Err(_) => return -2,
        };

        if buffer.set_pixel(x as u32, y as u32, &pixel_bytes) {
            0  // 成功
        } else {
            -3  // 参数错误
        }
    }
}

#[no_mangle]
pub extern "system" fn Java_ImageProcessor_getBufferData(
    mut env: JNIEnv,
    _class: JClass,
    ptr: jlong,
) -> jbyteArray {
    if ptr == 0 {
        return std::ptr::null_mut();
    }

    unsafe {
        let buffer = &*(ptr as *const ImageBuffer);

        match env.byte_array_from_slice(&buffer.data) {
            Ok(array) => array.into_raw(),
            Err(_) => std::ptr::null_mut(),
        }
    }
}

对应的 Java 代码:

public class ImageProcessor {
    private long bufferPtr;

    public ImageProcessor(int width, int height, int channels) {
        this.bufferPtr = createBuffer(width, height, channels);
        if (this.bufferPtr == 0) {
            throw new RuntimeException("Failed to create image buffer");
        }
    }

    public void setPixel(int x, int y, byte[] pixelData) {
        int result = setPixel(bufferPtr, x, y, pixelData);
        if (result != 0) {
            throw new RuntimeException("Failed to set pixel: " + result);
        }
    }

    public byte[] getBufferData() {
        return getBufferData(bufferPtr);
    }

    @Override
    public void close() {
        if (bufferPtr != 0) {
            destroyBuffer(bufferPtr);
            bufferPtr = 0;
        }
    }

    @Override
    protected void finalize() throws Throwable {
        close();
        super.finalize();
    }

    private native long createBuffer(int width, int height, int channels);
    private native void destroyBuffer(long ptr);
    private native int setPixel(long ptr, int x, int y, byte[] pixelData);
    private native byte[] getBufferData(long ptr);
}

性能考虑

内存分配开销

use std::time::Instant;

fn performance_comparison() {
    const ITERATIONS: usize = 1_000_000;

    // 测试1:正常的 Box 创建和释放
    let start = Instant::now();
    for _ in 0..ITERATIONS {
        let boxed = Box::new(42u64);
        // 自动释放
        drop(boxed);
    }
    let normal_duration = start.elapsed();

    // 测试2:使用 into_raw/from_raw
    let start = Instant::now();
    for _ in 0..ITERATIONS {
        let boxed = Box::new(42u64);
        let raw_ptr = Box::into_raw(boxed);
        unsafe {
            let restored = Box::from_raw(raw_ptr);
            drop(restored);
        }
    }
    let raw_duration = start.elapsed();

    println!("正常 Box 用时: {:?}", normal_duration);
    println!("into_raw/from_raw 用时: {:?}", raw_duration);
    println!("开销比率: {:.2}x", raw_duration.as_nanos() as f64 / normal_duration.as_nanos() as f64);
}

内存对齐和布局

use std::mem;

fn memory_layout_analysis<T>() {
    println!("类型 {}: ", std::any::type_name::<T>());
    println!("  大小: {} 字节", mem::size_of::<T>());
    println!("  对齐: {} 字节", mem::align_of::<T>());
    println!("  Box<T> 大小: {} 字节", mem::size_of::<Box<T>>());
    println!("  *mut T 大小: {} 字节", mem::size_of::<*mut T>());
    println!();
}

fn layout_demonstration() {
    memory_layout_analysis::<u8>();
    memory_layout_analysis::<u64>();
    memory_layout_analysis::<String>();
    memory_layout_analysis::<Vec<i32>>();
    memory_layout_analysis::<[u8; 1024]>();
}

调试和故障排除

内存泄露检测

use std::sync::atomic::{AtomicUsize, Ordering};

static ALLOC_COUNT: AtomicUsize = AtomicUsize::new(0);
static DEALLOC_COUNT: AtomicUsize = AtomicUsize::new(0);

struct TrackedBox<T> {
    ptr: *mut T,
    id: usize,
}

impl<T> TrackedBox<T> {
    fn new(value: T) -> Self {
        let boxed = Box::new(value);
        let ptr = Box::into_raw(boxed);
        let id = ALLOC_COUNT.fetch_add(1, Ordering::Relaxed);

        println!("分配 TrackedBox #{}: {:p}", id, ptr);

        TrackedBox { ptr, id }
    }

    fn leak(self) -> *mut T {
        let ptr = self.ptr;
        std::mem::forget(self);  // 防止 Drop
        println!("泄露 TrackedBox #{}: {:p}", self.id, ptr);
        ptr
    }
}

impl<T> Drop for TrackedBox<T> {
    fn drop(&mut self) {
        unsafe {
            let _boxed = Box::from_raw(self.ptr);
            DEALLOC_COUNT.fetch_add(1, Ordering::Relaxed);
            println!("释放 TrackedBox #{}: {:p}", self.id, self.ptr);
        }
    }
}

fn memory_leak_detection() {
    println!("=== 内存泄露检测演示 ===");

    {
        let tracked1 = TrackedBox::new(String::from("正常释放"));
        let tracked2 = TrackedBox::new(vec![1, 2, 3]);

        // tracked2 被泄露
        let _leaked_ptr = tracked2.leak();

        // tracked1 正常释放
    }

    let allocated = ALLOC_COUNT.load(Ordering::Relaxed);
    let deallocated = DEALLOC_COUNT.load(Ordering::Relaxed);

    println!("总分配: {}", allocated);
    println!("总释放: {}", deallocated);
    println!("泄露数量: {}", allocated - deallocated);
}

最佳实践总结

1. 配对使用原则

// ✅ 正确:严格配对
fn correct_pairing() {
    let boxed = Box::new(42);
    let raw_ptr = Box::into_raw(boxed);  // 转换

    unsafe {
        let restored = Box::from_raw(raw_ptr);  // 恢复
        // 自动释放
    }
}

// ❌ 错误:缺少配对
fn incorrect_usage() {
    let boxed = Box::new(42);
    let _raw_ptr = Box::into_raw(boxed);  // 泄露!
    // 没有对应的 from_raw 调用
}

2. 错误处理模式

fn robust_pointer_management<T>(value: T) -> Result<*mut T, &'static str> {
    let boxed = Box::new(value);
    let raw_ptr = Box::into_raw(boxed);

    // 验证指针
    if raw_ptr.is_null() {
        return Err("指针分配失败");
    }

    Ok(raw_ptr)
}

unsafe fn safe_restore<T>(ptr: *mut T) -> Result<Box<T>, &'static str> {
    if ptr.is_null() {
        return Err("空指针不能恢复");
    }

    // 这里应该有更多验证逻辑...
    Ok(Box::from_raw(ptr))
}

3. 文档和注释

/// 创建一个可以跨 FFI 边界传递的字符串指针
///
/// # 安全性
/// 调用者必须确保在适当的时候调用 `destroy_string` 来释放内存
///
/// # 示例
/// ```
/// let ptr = create_string("Hello".to_string());
/// // ... 使用 ptr ...
/// destroy_string(ptr);
/// ```
fn create_string(s: String) -> *mut String {
    Box::into_raw(Box::new(s))
}

/// 释放由 `create_string` 创建的字符串
///
/// # 安全性
/// - `ptr` 必须是由 `create_string` 返回的有效指针
/// - `ptr` 只能被释放一次
/// - 调用后 `ptr` 变为无效
unsafe fn destroy_string(ptr: *mut String) {
    if !ptr.is_null() {
        let _boxed = Box::from_raw(ptr);
    }
}

总结

Box::into_rawBox::from_rawstd::mem 模块形成了 Rust 手动内存管理的核心体系:

  1. Box::into_raw 提供了从自动管理到手动管理的安全转换
  2. Box::from_raw 提供了从手动管理回到自动管理的机制
  3. std::mem 模块 提供了补充的内存操作工具(forgetManuallyDroptransmute 等)
  4. 配合使用 可以实现精确的内存生命周期控制

关键要点:
- 严格配对使用 into_rawfrom_raw
- 优先使用 Box::into_raw 而不是 mem::forget 进行所有权转移
- 使用 ManuallyDrop 获得更精确的析构控制
- 在 FFI 和特殊场景中谨慎使用这些工具
- 实施适当的调试和检测机制防止内存错误

通过正确理解和使用这些工具,可以在保持 Rust 安全性的同时获得 C 语言级别的内存控制能力。

点赞

发表评论

昵称和uid可以选填一个,填邮箱必填(留言回复后将会发邮件给你)
tips:输入uid可以快速获得你的昵称和头像

Title - Artist
0:00