跳转至

Rust - MaybeUninit未初始化内存

Rust 1.39中废弃了std::mem::uninitialized(也即core::mem::uninitialized),因为它可能引发未定义行为。现在应当使用std::mem::MaybeUninit(也即core::mem::MaybeUninit)替代之。

未定义的行为

最简单的例子就是未初始化的bool

use std::mem;

let bool_value = unsafe { mem::uninitialized::<bool>() };
// 错误:在 bool_value 真正初始化之前就使用它的值
if bool_value {
  // ......
}

在目标平台上,未初始化的内存可能是不确定值,也有可能有某种初始值,例如全零、烫烫烫烫烫屯屯屯屯屯。无论如何,在Rust语言的层面,在bool_value真正初始化之前,它的值是不确定的。如果其实际值为零,就会被当成false;如果其实际值非零,就会被当成true(1)。此时使用bool_value就会引发未定义的行为。

对开发人员的要求

可见,mem::uninitialized需要开发人员自行保证,不要「在真正初始化之前使用未初始化内存」。

这种错误用法很有可能是隐晦的:

use std::io::{self, Read};
use std::mem;

#[repr(packed, C)]
struct MyRecord {
    field1: u16,
    field2: u32,
}

impl MyRecord {
    pub unsafe fn read_from<R: Read>(src: &mut R) -> io::Result<MyRecord> {
        let mut record = mem::uninitialized::<MyRecord>();
        src.read_exact(
            std::slice::from_raw_parts_mut(
                &mut record as *mut _ as *mut u8,
                mem::size_of::<MyRecord>()
            )
        )?;
        Ok(record)
    }
}

impl Drop for MyRecord {
    fn drop(&mut self) {
        // ......
    }
}

如果read_exact出错,就会在record没有有效值的情况下调用drop。 为此,必须先将内容保存到一个没有drop行为的结构,然后在获取了有效值之后再构造需要的结构:

impl MyRecord {
    pub unsafe fn read_from<R: Read>(src: &mut R) -> io::Result<MyRecord> {
        let mut raw = [0u8; mem::size_of::<MyRecord>()];
        src.read_exact(&mut raw)?;
        Ok(mem::transmute(raw))
    }
}

mem::MaybeUninit

Rust官方推荐用mem::MaybeUninit来实现未初始化的内存:

impl MyRecord {
    pub unsafe fn read_from<R: Read>(src: &mut R) -> io::Result<MyRecord> {
        let mut record = mem::MaybeUninit::<MyRecord>::uninit();
        src.read_exact(
            std::slice::from_raw_parts_mut(
                record.as_mut_ptr() as *mut u8,
                mem::size_of::<MyRecord>()
            )
        )?;
        Ok(record.assume_init())
    }
}

MaybeUninit<MyRecord>本身的失效并不会触发MyRecorddrop,因此MaybeUninit::uninit是safe的。

mem::MaybeUninit是用mem::ManuallyDrop实现的(这里省略了一些细节):

#[lang = "manually_drop"]
struct ManuallyDrop<T> { value: T }

struct MaybeUninit<T> {
    value: ManuallyDrop<T>,
    // ......
}

在对应的lang item实现之前,ManuallyDrop是用union实现的:

#[allow(unions_with_drop_fields)]
union ManuallyDrop<T> { value: T }

这利用了「即使union的field是Drop的,编译器也不会隐式为union实现Drop」这一特点。


只有1才会被认为是true,其他非零值作为bool都是未定义行为。