写入 MaybeUninit 结构中的字段?

dub*_*jim 5 unsafe initialization undefined-behavior rust

我正在用MaybeUninitRust 和 FFI做一些似乎有效的事情,但我怀疑可能不健全/依赖于未定义的行为。

我的目标是让一个 structMoreA扩展一个 struct A,包括A作为初始字段。然后调用一些写入 struct 的 C 代码A。然后MoreA根据A.

在我的应用程序中, 的附加字段MoreA都是整数,因此我不必担心分配给它们的(未初始化的)先前值。

这是一个最小的例子:

use core::fmt::Debug;
use std::mem::MaybeUninit;

#[derive(Clone, Copy, PartialEq, Debug)]
#[repr(C)]
struct A(i32, i32);

#[derive(Clone, Copy, PartialEq, Debug)]
#[repr(C)]
struct MoreA {
    head: A,
    more: i32,
}

unsafe fn mock_ffi(p: *mut A) {
    // write doesn't drop previous (uninitialized) occupant of p
    p.write(A(1, 2));
}

fn main() {
    let mut b = MaybeUninit::<MoreA>::uninit();
    unsafe { mock_ffi(b.as_mut_ptr().cast()); }
    let b = unsafe {
        let mut b = b.assume_init();
        b.more = 3;
        b
    };
    assert_eq!(&b, &MoreA { head: A(1, 2), more: 3 });
}
Run Code Online (Sandbox Code Playgroud)

代码let b = unsafe { ... }好听吗?它运行正常并且Miri 没有抱怨

MaybeUninit文档说:

此外,未初始化内存的特殊性在于编译器知道它没有固定值。这使得在变量中包含未初始化数据的行为是未定义行为,即使该变量具有整数类型,否则可以保存任何固定位模式。

此外,Rust 书说被认为未定义的行为包括:

  • 产生无效值,即使在私有字段和本地。“产生”一个值发生在任何时候一个值被分配到一个地方或从一个地方读取,传递给一个函数/原语操作或从一个函数/原语操作返回。以下值无效(在它们各自的类型中):

    ... 一个整数 (i*/u*) 或 ... 从未初始化的内存中获得。

在另一方面,它似乎并不能够写入到more现场之前调用assume_init。稍后在同一页面上:

目前尚不支持在 MaybeUninit 中创建原始指针或对结构字段的引用的方法。这意味着无法通过调用 MaybeUninit::uninit::() 然后写入其字段来创建结构。

如果我在上面的代码示例中所做的确实触发了未定义的行为,那么解决方案是什么?

  1. 我想避免装箱 A 值(也就是说,我想将它直接包含在 中MoreA)。

  2. 我还希望避免必须创建一个A传递给mock_ffi然后将结果复制MoreA. A在我的实际应用中是一个大型结构。

我想如果没有可靠的方法来获得我所追求的东西,我将不得不选择这两个后备之一。

如果 struct A 是一种可以将位模式 0 保存为有效值的类型,那么我猜第三个回退是:

  1. 从而MaybeUninit::zeroed()不是开始MaybeUninit::uninit()

Pet*_*all 5

目前,引用任何类型的未初始化内存的唯一合理方法是MaybeUninit. 实际上,读取或写入未初始化的整数可能是安全的,但这并没有正式记录。这肯定是没有安全读取或写入到一个未初始化bool或大多数其他类型。

通常,如文档所述,您不能逐个字段初始化结构字段。但是,只要符合以下条件,这样做是合理的:

  1. 该结构具有repr(C). 这是必要的,因为它阻止了 Rust 进行巧妙的布局技巧,以便 type 字段MaybeUninit<T>的布局与 type 字段的布局保持相同T,无论其相邻字段如何。
  2. 每个字段都是MaybeUninit. 这让我们assume_init()可以使用整个结构,然后单独初始化每个字段。

鉴于您的结构已经是repr(C),您可以使用MaybeIninit用于每个字段的中间表示。这repr(C)也意味着一旦初始化,我们就可以在类型之间进行转换,前提是两个结构具有相同顺序的相同字段。

use std::mem::{self, MaybeUninit};

#[repr(C)]
struct MoreAConstruct {
    head: MaybeUninit<A>,
    more: MaybeUninit<i32>,
}

let b: MoreA = unsafe {
    // It's OK to assume a struct is initialized when all of its fields are MaybeUninit
    let mut b_construct = MaybeUninit::<MoreAConstruct>::uninit().assume_init();
    mock_ffi(b_construct.head.as_mut_ptr());
    b_construct.more = MaybeUninit::new(3);
    mem::transmute(b_construct)
};
Run Code Online (Sandbox Code Playgroud)