内存布局 Memory Layout

与结构体和枚举相比, 联合体的内存布局是最特殊的.

前面的章节提到了, 联合体的特点:

  • 联合体中各个成员共享同一块内存, 其内存大小是占用内存最大的那个元素
  • 当向一个成员写入值时, 可能就会把别的成员的内容给覆盖

具体是怎么共享内存的, 我们先看个例子:

#![allow(non_camel_case_types)]

use std::ffi::c_void;

#[repr(C)]
pub union literals_t {
    pub v_u8: u8,
    pub v_u16: u16,
    pub v_u32: u32,
    pub v_u64: u64,
    pub v_char: char,
    pub v_ptr: *const c_void,
}

impl literals_t {
    pub const NULL_PTR: Self = Self { v_u64: 0 };

    /// 将所有字节都重置为 0
    #[inline]
    pub fn reset(&mut self) {
        self.v_u64 = 0;
    }
}

fn main() {
    assert_eq!(size_of::<literals_t>(), 8);

    // 初始化时, 不是 unsafe的, 这里只写入其中 1 个字节, 其它 7 个字节位未初始化.
    let value = literals_t { v_u8: 42 };

    // 访问成员的值是, 是 unsafe
    unsafe {
        assert_eq!(value.v_u8, 0x2a);
    }

    let v_u64 = unsafe { value.v_u64 };
    let least_byte = (v_u64 & 0xff) as u8;
    assert_eq!(least_byte, 42);

    // 访问其它字节, 是未定义行为, 因为它们都没有被初始化.
    let _most_byte = (v_u64 >> 24) as u8;
}

上面的代码中, 因为只初始化了最低位的字节, 其它 7 个字节仍然是未初始化的. 因为 literals_t 中的所有成员共享了8个字节的内存.

调试代码也可以发现,

v_u64 value

注意, 当前系统是小端系统, 所以最低有效位在左侧, 最高有效位在右侧.

所以: v_u64 的值是 0x0000_5578_7020_192a, 可以发现它的最低位确实是我们事先设置的 0x2a, 但是其余 7 个字节都是不确定的, 因为它们没有被初始化!

literals layout

总结一下:

  • 初始化联合体时, 应该先将它所有字节重置为0, 再初始化某个联合体成员, 以减少未定义行为发生