从文件中读取原始结构的最佳方法
Best way to read a raw struct from a file
背景(可跳过)
在 linux 上,文件 /var/run/utmp
包含几个 utmp
结构,每个结构都是原始二进制格式,在文件中一个接一个。 utmp
本身就是一个比较大的(在我的机器上是384字节)。我正在尝试将此文件读取为原始数据,并且他们在数据有意义之后实施检查。我对 Rust 并不陌生,但这是我第一次真正体验到不安全的一面。
问题陈述
我有一个包含几个 c sturct utmp
s (docs) 的文件。在 Rust 中,我想将整个文件读入 Vec<libc::utmpx>
的数组中。更具体地说,给定一个 reader 打开这个文件,我怎么能读一个 struct utmp
?
到目前为止我有什么
下面是 read_raw
的三种不同实现,它接受 reader 和 returns 和 RawEntry
(我对 struct utmp
的别名)。哪种方法最正确?我正在尝试编写尽可能高效的代码,而且我担心 read_raw0
如果涉及 memcpys,可能会比其他代码慢。 best/fastest 完成此行为的方法是什么?
use std::io::Read;
use libc::utmpx as RawEntry;
const RawEntrySize = std::mem::size_of::<RawEntry>();
type RawEntryBuffer = [u8; RawEntrySize];
/// Read a raw utmpx struct
// After testing, this method doesn't work
pub fn read_raw0<R: Read>(reader: &mut R) -> RawEntry {
let mut entry: RawEntry = unsafe { std::mem::zeroed() };
unsafe {
let mut entry_buf = std::mem::transmute::<RawEntry, RawEntryBuffer>(entry);
reader.read_exact(&mut entry_buf[..]);
}
return entry;
}
/// Read a raw utmpx struct
pub fn read_raw1<R: Read>(reader: &mut R) -> RawEntry {
// Worried this could cause alignment issues, or maybe it's okay
// because transmute copies
let mut buffer: RawEntryBuffer = [0; RawEntrySize];
reader.read_exact(&mut buffer[..]);
let entry = unsafe {
std::mem::transmute::<RawEntryBuffer, RawEntry>(buffer)
};
return entry;
}
/// Read a raw utmpx struct
pub fn read_raw2<R: Read>(reader: &mut R) -> RawEntry {
let mut entry: RawEntry = unsafe { std::mem::zeroed() };
unsafe {
let entry_ptr = std::mem::transmute::<&mut RawEntry, *mut u8>(&mut entry);
let entry_slice = std::slice::from_raw_parts_mut(entry_ptr, RawEntrySize);
reader.read_exact(entry_slice);
}
return entry;
}
注意:经过更多测试,似乎 read_raw0
不起作用。我相信这是因为 transmute 创建了一个新缓冲区而不是引用结构。
这是我想出的,我想它应该和读取单个条目一样快。它遵循您上一个条目的精神,但避免了转化(将 &mut T
转化为 *mut u8
可以通过两次转换来完成:t as *mut T as *mut u8
)。它还使用 MaybeUninit
而不是 zeroed
更明确一点(程序集在优化后可能是相同的)。最后,该函数无论哪种方式都是不安全的,因此我们不妨将其标记为不安全,并取消 unsafe
块。
use std::io::{self, Read};
use std::slice::from_raw_parts_mut;
use std::mem::{MaybeUninit, size_of};
pub unsafe fn read_raw_struct<R: Read, T: Sized>(src: &mut R) -> io::Result<T> {
let mut buffer = MaybeUninit::uninit();
let buffer_slice = from_raw_parts_mut(buffer.as_mut_ptr() as *mut u8, size_of::<T>());
src.read_exact(buffer_slice)?;
Ok(buffer.assume_init())
}
背景(可跳过)
在 linux 上,文件 /var/run/utmp
包含几个 utmp
结构,每个结构都是原始二进制格式,在文件中一个接一个。 utmp
本身就是一个比较大的(在我的机器上是384字节)。我正在尝试将此文件读取为原始数据,并且他们在数据有意义之后实施检查。我对 Rust 并不陌生,但这是我第一次真正体验到不安全的一面。
问题陈述
我有一个包含几个 c sturct utmp
s (docs) 的文件。在 Rust 中,我想将整个文件读入 Vec<libc::utmpx>
的数组中。更具体地说,给定一个 reader 打开这个文件,我怎么能读一个 struct utmp
?
到目前为止我有什么
下面是 read_raw
的三种不同实现,它接受 reader 和 returns 和 RawEntry
(我对 struct utmp
的别名)。哪种方法最正确?我正在尝试编写尽可能高效的代码,而且我担心 read_raw0
如果涉及 memcpys,可能会比其他代码慢。 best/fastest 完成此行为的方法是什么?
use std::io::Read;
use libc::utmpx as RawEntry;
const RawEntrySize = std::mem::size_of::<RawEntry>();
type RawEntryBuffer = [u8; RawEntrySize];
/// Read a raw utmpx struct
// After testing, this method doesn't work
pub fn read_raw0<R: Read>(reader: &mut R) -> RawEntry {
let mut entry: RawEntry = unsafe { std::mem::zeroed() };
unsafe {
let mut entry_buf = std::mem::transmute::<RawEntry, RawEntryBuffer>(entry);
reader.read_exact(&mut entry_buf[..]);
}
return entry;
}
/// Read a raw utmpx struct
pub fn read_raw1<R: Read>(reader: &mut R) -> RawEntry {
// Worried this could cause alignment issues, or maybe it's okay
// because transmute copies
let mut buffer: RawEntryBuffer = [0; RawEntrySize];
reader.read_exact(&mut buffer[..]);
let entry = unsafe {
std::mem::transmute::<RawEntryBuffer, RawEntry>(buffer)
};
return entry;
}
/// Read a raw utmpx struct
pub fn read_raw2<R: Read>(reader: &mut R) -> RawEntry {
let mut entry: RawEntry = unsafe { std::mem::zeroed() };
unsafe {
let entry_ptr = std::mem::transmute::<&mut RawEntry, *mut u8>(&mut entry);
let entry_slice = std::slice::from_raw_parts_mut(entry_ptr, RawEntrySize);
reader.read_exact(entry_slice);
}
return entry;
}
注意:经过更多测试,似乎 read_raw0
不起作用。我相信这是因为 transmute 创建了一个新缓冲区而不是引用结构。
这是我想出的,我想它应该和读取单个条目一样快。它遵循您上一个条目的精神,但避免了转化(将 &mut T
转化为 *mut u8
可以通过两次转换来完成:t as *mut T as *mut u8
)。它还使用 MaybeUninit
而不是 zeroed
更明确一点(程序集在优化后可能是相同的)。最后,该函数无论哪种方式都是不安全的,因此我们不妨将其标记为不安全,并取消 unsafe
块。
use std::io::{self, Read};
use std::slice::from_raw_parts_mut;
use std::mem::{MaybeUninit, size_of};
pub unsafe fn read_raw_struct<R: Read, T: Sized>(src: &mut R) -> io::Result<T> {
let mut buffer = MaybeUninit::uninit();
let buffer_slice = from_raw_parts_mut(buffer.as_mut_ptr() as *mut u8, size_of::<T>());
src.read_exact(buffer_slice)?;
Ok(buffer.assume_init())
}