如何将结构数组放置在另一个结构的末尾而不进行内存分段

Joh*_*ith 3 memory memory-management rust

我正在将低延迟实时应用程序移植到 Rust。本质上我想在不复制的情况下执行尽可能多的操作。目前内存分配的主要来源来自网络堆栈。每次接收或发送数据包时,都会在幕后进行大量复制,以强制从 Rust 结构中检索字节。我想停止这样做。

本质上我希望能够将数据包从字节转换为字节并传输它。下面是一个无法正常工作的示例代码,它说明了我想要做的事情。

#[repr(C, align(4))]
#[derive(Debug)]
struct Content {
    first: u8,
    padding: [u8; 3]
}

#[repr(C, align(4))]
#[derive(Debug)]
struct Packet {
    header_id: u16,
    header_padding: [u8; 2],
    // This is another pointer I want it to be located after header_padding
    content: Box<[Content]>,
}

#[derive(Debug)]
struct ParsedPacket {
    content_length: usize,
    // The box below should directly reference the original u8 bytes without copying
    packet: Box<Packet>,
}

unsafe fn buf_to_packet(bs: Box<[u8]>) -> Option<Box<ParsedPacket>> {
    // How to implement this
    // First 4 bytes are the header
    // The rest must be 4 byte multiple and each 4 byte is a Content struct
    // So from the length of the boxed slice we can infer the content_length
    todo!()
}

unsafe fn packet_to_buf(bs: ParsedPacket) -> Box<[u8]> {
    todo!()
}

fn main() {
    let bytes = vec![
        // Header
        0, 23, 0, 0,
        // Content 0
        0, 0, 0, 0,
        // Content 1
        1, 0, 0, 0,
        // Content 2
        0, 0, 0, 0
    ].into_boxed_slice();

    let packet = unsafe { buf_to_packet(bytes) };

    println!("{:?}", content);
}
Run Code Online (Sandbox Code Playgroud)

这有两个问题。Rust 要求内容是一个装箱切片。它的主干不一定位于结构本身,但可能位于其他地方。此外,盒装切片还包含其自己的长度簿记。我想我需要自己对长度进行簿记。但我不知道如何在不使用盒装切片的情况下添加具有动态长度的结构成员。

我当然愿意用unsafe来做这件事。但我对 Rust 没有经验,所以我不知道如何实际实现它。

本质上我在 Rust 中寻找的是来自 C 的灵活数组成员。

kmd*_*eko 7

在 Rust 中,这称为动态大小类型(DST),它通常涵盖特征对象和切片,但您可以制作自定义 DST,即使最多支持不稳定(通过使用unsafe夜间功能来实现此类功能)。

为了使你的结构成为一个自定义的 DST(并且行为类似于 C 中的灵活数组成员),你只需将最后一个成员变成一个裸切片,如下所示:

#[repr(C, align(4))]
#[derive(Debug)]
struct Packet {
    header_id: u16,
    header_padding: [u8; 2],
    content: [Content],
}
Run Code Online (Sandbox Code Playgroud)

要将字节“转换”为字节,Packet必须首先对齐字节。在许多现代系统上,这通常已经是 8 字节对齐的,但如果不能依赖这一点,您可以使用此处的技巧:How do I allocate a Vec that isaligned to the size of the cache line? 由于对齐和分配限制,创建 比&Packet创建Box<Packet>.

然后,为了正确操作用于表示间接 DST 的“胖指针”,我们需要依赖不稳定的功能ptr_metadata。使用它会产生以下结果:

#![feature(ptr_metadata, pointer_is_aligned)]

#[repr(C, align(4))]
#[derive(Debug)]
struct Content {
    first: u8,
    padding: [u8; 3]
}

#[repr(C, align(4))]
#[derive(Debug)]
struct Packet {
    header_id: u16,
    header_padding: [u8; 2],
    content: [Content],
}

fn buf_to_packet(bytes: &[u8]) -> Option<&Packet> {
    const PACKET_HEADER_LEN: usize = 4;

    let bytes_len = bytes.len();
    let bytes_ptr = bytes.as_ptr();
    if bytes_len < PACKET_HEADER_LEN || !bytes_ptr.is_aligned_to(4) {
        return None;
    }

    let content_len = (bytes_len - PACKET_HEADER_LEN) / std::mem::size_of::<Content>();
    let packet_raw: *const Packet = std::ptr::from_raw_parts(bytes_ptr.cast(), content_len);

    // SAFETY: the pointer is aligned and initialized
    unsafe { packet_raw.as_ref() }
}
Run Code Online (Sandbox Code Playgroud)

也可以看看: