在我的项目的相当低级的部分中,函数接收可变的原始数据切片(&mut [u32]在这种情况下)。此数据应以小端序写入作者。
现在,仅此一项就不成问题,但是所有这些必须快速。我评估了我的应用程序,并将其确定为关键路径之一。特别是,如果不需要更改字节序(因为我们已经在一个小的字节序系统上),那么就不会有任何开销。
这是我的代码(Playground):
use std::{io, mem, slice};
fn write_data(mut w: impl io::Write, data: &mut [u32]) -> Result<(), io::Error> {
adjust_endianness(data);
// Is this safe?
let bytes = unsafe {
let len = data.len() * mem::size_of::<u32>();
let ptr = data.as_ptr() as *const u8;
slice::from_raw_parts(ptr, len)
};
w.write_all(bytes)
}
fn adjust_endianness(_: &mut [u32]) {
// implementation omitted
}
Run Code Online (Sandbox Code Playgroud)
adjust_endianness更改位置的字节序(这很好,因为错误的字节序u32是垃圾,但仍然是有效的u32)。
该代码有效,但关键问题是:这样安全吗?特别是,在某个时刻,data并且bytes两者都存在,它们是同一数据的一个可变且一个不变的切片。听起来很不好,对吧?
另一方面,我可以这样做:
let bytes = &data[..];
Run Code Online (Sandbox Code Playgroud)
这样,我也有这两个方面。所不同的只是data现在借来的。
我的代码安全吗,或者显示UB?为什么?如果不安全,该如何安全地做我想做的事情?
一般来说,创建违反 Rust 安全规则的切片,即使是短暂的,也是不安全的。如果你欺骗借用检查器并同时制作独立的切片借用与&和&mut相同的数据,这将使 Rust 在 LLVM 中指定不正确的别名信息,这可能会导致实际错误编译的代码。Miri不会标记这种情况,因为您data之后不会使用,但不安全的具体细节仍在制定中。
为了安全起见,您应该向借阅检查员解释共享情况:
let shared_data = &data[..];
Run Code Online (Sandbox Code Playgroud)
datashared_data在使用期间将暂时重新借用为共享/只读。在这种情况下,它不应该造成任何限制。退出此范围后,意志data将保持可变状态。
那么你就会有&[u32],但你需要&[u8]。幸运的是,这种转换是安全的,因为两者都是共享的,并且u8比它具有更少的对齐要求u32(如果是其他方式,则必须使用align_to!)。
let shared_data = &data[..];
let bytes = unsafe {
let len = shared_data.len() * mem::size_of::<u32>();
let ptr = data.as_ptr() as *const u8;
slice::from_raw_parts(ptr, len)
};
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
165 次 |
| 最近记录: |