You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为C库实现Rust封装时避免Drop重复调用引发未定义行为

解决Rust封装C库时的双重释放问题

问题背景

我正在为VMAF这个C库构建Rust封装。C库的结构体通常配套内存分配与释放函数,原本计划在Rust封装类型的构造函数中调用C的分配逻辑,在Drop trait中执行释放操作。但遇到一个问题:部分C函数(比如vmaf_read_pictures)会在内部调用释放函数消耗传入的结构体,当Rust封装实例超出作用域时,Drop中的释放代码会被二次执行,引发未定义行为。


解决方案

方案1:用std::mem::ManuallyDrop手动管控生命周期

ManuallyDrop可以禁用Rust的自动Drop调用,让你完全手动控制释放时机。当调用会消耗结构体的C函数后,可通过ManuallyDrop::into_inner取出内部值,避免后续Drop触发。

示例Rust代码:

use std::mem::ManuallyDrop;

#[repr(C)]
pub struct VmafPicture {
    pub pix_fmt: u32,
    pub bpc: u32,
    pub w: [u32; 3],
    pub h: [u32; 3],
    pub stride: [isize; 3],
    pub data: [*mut u8; 3],
    pub ref_: *mut (), // 对应C的VmafRef*
}

pub struct VmafPictureWrapper {
    inner: ManuallyDrop<VmafPicture>,
}

impl VmafPictureWrapper {
    // 构造函数:调用C的分配函数
    pub fn new(pix_fmt: u32, bpc: u32, w: u32, h: u32) -> Result<Self, i32> {
        let mut pic = unsafe { std::mem::zeroed::<VmafPicture>() };
        let ret = unsafe { vmaf_picture_alloc(&mut pic, pix_fmt, bpc, w, h) };
        if ret != 0 {
            return Err(ret);
        }
        Ok(Self { inner: ManuallyDrop::new(pic) })
    }

    // 获取内部指针供C函数调用
    pub fn as_ptr(&mut self) -> *mut VmafPicture {
        &mut *self.inner
    }

    // 手动释放:确认需要自行释放时调用
    pub fn unref(mut self) -> Result<(), i32> {
        let ret = unsafe { vmaf_picture_unref(&mut *self.inner) };
        let _ = ManuallyDrop::into_inner(self.inner); // 取出内部值,避免Drop触发
        ret.eq(&0).then_some(()).ok_or(ret)
    }
}

impl Drop for VmafPictureWrapper {
    fn drop(&mut self) {
        // 检查ref_是否为空,因为vmaf_picture_unref会把结构体memset为0
        if !self.inner.ref_.is_null() {
            let _ = unsafe { vmaf_picture_unref(&mut *self.inner) };
        }
    }
}

// 使用示例:调用vmaf_read_pictures
fn use_vmaf_read_pictures(
    vmaf: *mut (),
    mut ref_pic: VmafPictureWrapper,
    mut dist_pic: VmafPictureWrapper
) -> Result<(), i32> {
    let ret = unsafe { vmaf_read_pictures(vmaf, ref_pic.as_ptr(), dist_pic.as_ptr(), 0) };
    if ret != 0 {
        return Err(ret);
    }
    // C函数已释放图片,Drop时会自动跳过释放逻辑
    Ok(())
}

方案2:内部添加所有权标志位

在封装结构体中加入owned布尔标志,标记当前Rust实例是否拥有C结构体的所有权。当调用会消耗结构体的C函数后,将owned设为false,Drop时仅在owned为true时执行释放。

示例Rust代码:

#[repr(C)]
pub struct VmafPicture {
    // 同方案1字段定义
}

pub struct VmafPictureWrapper {
    inner: VmafPicture,
    owned: bool,
}

impl VmafPictureWrapper {
    pub fn new(pix_fmt: u32, bpc: u32, w: u32, h: u32) -> Result<Self, i32> {
        let mut pic = unsafe { std::mem::zeroed::<VmafPicture>() };
        let ret = unsafe { vmaf_picture_alloc(&mut pic, pix_fmt, bpc, w, h) };
        if ret != 0 {
            return Err(ret);
        }
        Ok(Self { inner: pic, owned: true })
    }

    pub fn as_ptr(&mut self) -> *mut VmafPicture {
        &mut self.inner
    }

    // 标记结构体已被C函数消耗,放弃所有权
    pub fn mark_consumed(&mut self) {
        self.owned = false;
    }
}

impl Drop for VmafPictureWrapper {
    fn drop(&mut self) {
        if self.owned && !self.inner.ref_.is_null() {
            let _ = unsafe { vmaf_picture_unref(&mut self.inner) };
        }
    }
}

// 使用示例
fn use_vmaf_read_pictures(
    vmaf: *mut (),
    mut ref_pic: VmafPictureWrapper,
    mut dist_pic: VmafPictureWrapper
) -> Result<(), i32> {
    let ret = unsafe { vmaf_read_pictures(vmaf, ref_pic.as_ptr(), dist_pic.as_ptr(), 0) };
    if ret != 0 {
        return Err(ret);
    }
    // 标记图片已被消耗
    ref_pic.mark_consumed();
    dist_pic.mark_consumed();
    Ok(())
}

方案3:结合C库的引用计数特性

从vmaf_picture_unref的实现可以看到,它内部使用引用计数管理内存,只有当计数归0时才会真正释放内存。但需要注意:vmaf_picture_unref最后会把结构体memset为0,即使计数未归0,结构体内容也会被清空,后续访问会触发未定义行为。因此该方案仍需结合前面的所有权管控逻辑使用,仅作为辅助验证手段。


相关C代码参考

结构体定义(picture.h)

#ifndef __VMAF_PICTURE_H__
#define __VMAF_PICTURE_H__

#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif

enum VmafPixelFormat {
    VMAF_PIX_FMT_UNKNOWN,
    VMAF_PIX_FMT_YUV420P,
    VMAF_PIX_FMT_YUV422P,
    VMAF_PIX_FMT_YUV444P,
    VMAF_PIX_FMT_YUV400P,
};

typedef struct VmafRef VmafRef;

typedef struct {
    enum VmafPixelFormat pix_fmt;
    unsigned bpc;
    unsigned w[3], h[3];
    ptrdiff_t stride[3];
    void *data[3];
    VmafRef *ref;
} VmafPicture;

int vmaf_picture_alloc(VmafPicture *pic, enum VmafPixelFormat pix_fmt,
                       unsigned bpc, unsigned w, unsigned h); // 为VmafPicture分配内存

int vmaf_picture_unref(VmafPicture *pic); // 释放VmafPicture的内存

#ifdef __cplusplus
}
#endif

#endif /* __VMAF_PICTURE_H__ */

触发问题的函数(vmaf_read_pictures)

int vmaf_read_pictures(VmafContext *vmaf, VmafPicture *ref, VmafPicture *dist,
                       unsigned index)
{
    if (!vmaf) return -EINVAL;
    if (vmaf->flushed) return -EINVAL;
    if (!ref != !dist) return -EINVAL;
    if (!ref && !dist) return flush_context(vmaf);

    int err = 0;

    vmaf->pic_cnt++;
    err = validate_pic_params(vmaf, ref, dist);
    if (err) return err;

    if (vmaf->thread_pool)
        return threaded_read_pictures(vmaf, ref, dist, index);

    for (unsigned i = 0; i < vmaf->registered_feature_extractors.cnt; i++) {
        VmafFeatureExtractorContext *fex_ctx =
            vmaf->registered_feature_extractors.fex_ctx[i];

        if ((vmaf->cfg.n_subsample > 1) && (index % vmaf->cfg.n_subsample) &&
            !(fex_ctx->fex->flags & VMAF_FEATURE_EXTRACTOR_TEMPORAL))
        {
            continue;
        }

        err = vmaf_feature_extractor_context_extract(fex_ctx, ref, NULL, dist,
                                                     NULL, index,
                                                     vmaf->feature_collector);
        if (err) return err;
    }

// 此处会释放传入的图片内存!
    err = vmaf_picture_unref(ref);
    if (err) return err;
    err = vmaf_picture_unref(dist);
    if (err) return err;

    return 0;
}

vmaf_picture_unref实现

int vmaf_picture_unref(VmafPicture *pic) {
    if (!pic) return -EINVAL;
    if (!pic->ref) return -EINVAL;

    vmaf_ref_fetch_decrement(pic->ref);
    if (vmaf_ref_load(pic->ref) == 0) {
        aligned_free(pic->data[0]);
        vmaf_ref_close(pic->ref);
    }
    memset(pic, 0, sizeof(*pic));
    return 0;
}

内容的提问来源于stack exchange,提问作者Brandon Piña

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 21:50:23