C中有一个 struct

struct Foo{
    unsigned int    body_len;
    unsigned char   body[1];    
}

问题是如何定义struct foo的Rust部分,以及如何创建一个Foo实例,该实例将传递给外部C函数,该函数接受foo*作为参数,以便可以在C部分中访问主体数据.主体成员将在c函数中转换为(无符号字符*).

推荐答案

这个 struct 声明的theoretically个最好的Rust类比是:

#[repr(C)]
struct Foo {
    body_len: std::ffi::c_uint,
    body: [std::ffi::c_uchar],
}

这是一个custom dynamically sized type;编译器知道它的大小是不同的.然而,有一个问题:处理这类类型涉及存储长度in the pointer, not in the struct的"胖指针"或"宽指针".因此,您会发现传递&Foo甚至*const Foo很困难,因为编译器会要求您构造具有冗余长度的指针(使用当前稳定的操作实际上很难做到这一点).

因此,为了处理internally的指定长度(即,在同一 struct 中的一个字段中),您需要放弃并采取基本上与C代码中相同的方法,即提供一个标记大于Foo的数据开始的伪字段.然而,与标准C不同,Rust不要求数组的大小至少为1,因此我们可以更合理地安排.

#[repr(C)]
struct Foo {
    body_len: std::ffi::c_uint,
    body: [std::ffi::c_uchar; 0],
}

然后,您可以使用指针操作来访问body.注意,要做到这一点是相当棘手的;您应该要求对您实际编写的任何代码进行代码审查,并且还应该在Miri以下测试您的代码(尽可能避免实际的FFI部分),以便有足够的信心确保它是健全和正确的.

下面是一个完整的例子,说明了如何做到这一点,提供了一个安全的BoxFoo类型,可以分配Foo并分发指向它们的指针.我自己并不是这种特殊的指针诡计的专家,所以请不要认为这绝对是最好的方法.

use std::{alloc, ptr};
use std::ffi::{c_uint, c_uchar};

#[repr(C)]
struct Foo {
    body_len: c_uint,
    body: [c_uchar; 0],
}

/// An owner for a heap allocated [`Foo`] that understands its length.
/// (We can't use `Box` because it assumes that `size_of::<Foo>()` is the entire size.)
struct BoxFoo {
    foo: *mut Foo,
}

impl BoxFoo {
    /// Computes the layout for allocating a `Foo` of a given size.
    fn layout(body_len: c_uint) -> alloc::Layout {
        let byte_len = usize::try_from(body_len).expect("body_len overflowed usize");
        let (layout, _) = alloc::Layout::new::<Foo>()
            .extend(alloc::Layout::array::<c_uchar>(byte_len).unwrap())
            .unwrap();
        layout
    }

    pub fn new(bytes_to_copy: &[c_uchar]) -> Self {
        let body_len: std::ffi::c_uint = bytes_to_copy.len().try_into().expect("body too long");
        let layout = Self::layout(body_len);

        // SAFETY:
        // * The preconditions of `alloc::alloc()` are met because we know `Foo` has nonzero size
        //   even if its data is nonzero.
        // * The pointer write operations write into memory we just allocated to be big enough.
        let foo = unsafe {
            // Allocate enough memory for the data.
            let ptr: *mut Foo = alloc::alloc(layout).cast::<Foo>();
            if ptr.is_null() {
                alloc::handle_alloc_error(layout);
            }

            // Initialize the fixed part of the struct
            ptr::write(ptr, Foo { body_len, body: [] });
            // Initialize the variable length part
            let body_array_ptr: *mut [c_uchar; 0] = ptr::addr_of_mut!((*ptr).body);
            std::ptr::copy_nonoverlapping(
                bytes_to_copy.as_ptr(),
                body_array_ptr.cast::<c_uchar>(),
                bytes_to_copy.len(),
            );
            ptr
        };

        Self { foo }
    }

    pub fn as_ptr(&self) -> *const Foo {
        self.foo
    }

    pub fn body(&self) -> &[c_uchar] {
        // SAFETY: `Foo` has the invariant that `body_len` shall be no longer than is
        // actually allocated after the `Foo` structure.
        unsafe {
            std::slice::from_raw_parts(
                ptr::addr_of!((*self.foo).body).cast::<c_uchar>(),
                usize::try_from((*self.foo).body_len).expect("body_len overflowed usize"),
            )
        }
    }
}

impl std::ops::Deref for BoxFoo {
    type Target = Foo;

    fn deref(&self) -> &Self::Target {
        // SAFETY: It is an invariant of this type that the pointer points to a valid Foo.
        unsafe { &*self.foo }
    }
}

impl Drop for BoxFoo {
    fn drop(&mut self) {
        let layout = Self::layout(unsafe { (*self.foo).body_len });
        // SAFETY: It is an invariant of this type that the pointer is a current allocation of
        // this layout.
        unsafe {
            alloc::dealloc(self.foo.cast::<u8>(), layout);
        }
    }
}

#[test]
fn alloc_use_dealloc() {
    let data = b"hello world";
    let foo = BoxFoo::new(data);
    assert_eq!(foo.body(), data);
    drop(foo);
}

C++相关问答推荐

以c格式打印时间戳

是否有任何情况(特定类型/值),类型双关在所有符合标准的C实现中产生相同的行为?

这是一个合法的C Strdup函数吗?

如果包含路径不存在,我的编译器可以被配置为出错吗?(GCC、MSVC)

我可以在C中声明不同长度数组的数组而不带变量名吗?

可以将C变量限制为特定的读/写速度吗?

如何在不使用其他数组或字符串的情况下交换字符串中的两个单词?

平均程序编译,但结果不好

如何计算打印二叉搜索树时每行所需的空间?

如何在提取的索引中分配空值?

在C中包装两个数组?

在C++中允许使用字符作为宏参数

仅从限制指针参数声明推断非混叠

初始成员、公共初始序列、匿名联合和严格别名如何在C中交互?

将 struct 数组写入二进制文件时发生Valgrind错误

当用C打印过多的';\n';时输出不正确

正在try 理解C++中的`正在释放的指针未被分配‘错误

struct 中的qsort,但排序后的 struct 很乱

C 错误:对 int 数组使用 typedef 时出现不兼容的指针类型问题

#define X Defined(Y) 是有效的 C/C++ 宏定义吗?