Rust OS 前置:属性与内存布局——repr(C)、repr(transparent) 与 derive

看 VGA 或驱动代码时会碰到 #[repr(C)]、#[repr(transparent)]、#[derive(...)] 这样的方括号标签。它们叫属性(attribute),作用是告诉编译器下面这个东西按什么特殊规则处理。其中 repr 系列直接决定 struct 在内存里的排布——这是和硬件打交道的关键。

接《Rust OS 前置:从 C 到 Rust》与《借用与生命周期》。

📚 基本概念速读

名称 含义 省流
#[...] 属性(attribute) 告诉编译器按特殊规则处理
#[derive(...)] 自动生成常用实现 加功能,不是加数据
#[repr(C)] 按 C 的内存布局排布 和硬件/FFI 对齐
#[repr(transparent)] 单字段类型与字段布局一致 包装不额外占内存

🏷️ 属性 #[...] 是什么

1
2
#[repr(C)]
struct ScreenChar { ... }

#[...] 不是函数调用,也不是注释,而是给编译器的指令:

下面这个东西,请按照某种特殊规则处理。

常见用途:#[derive(...)](自动实现 trait)、#[repr(...)](规定内存布局)、#[no_mangle](禁止符号名改写)、#![no_std](crate 级属性)——你在 Rust OS 项目里其实已经见过后面几个。

🪄 derive:让编译器自动生成功能

1
2
3
4
5
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct ScreenChar {
ascii_character: u8,
color_code: ColorCode,
}

derive 的意思是让编译器自动给这个类型实现这些 trait:

1
2
3
4
5
Debug      可以格式化打印
Clone 可以复制一份
Copy 赋值即复制(不移动)
PartialEq 可以用 == 比较
Eq 完整相等性

它给类型加功能,不改变数据本身。

🧬 repr(C):内存布局按 C 的规则

1
2
3
4
5
#[repr(C)]
struct ScreenChar {
ascii_character: u8, // 1 字节
color_code: ColorCode, // 1 字节
}

repr(C) 规定这个 struct 按 C 语言的内存布局规则排列(字段顺序、对齐方式都和 C 编译器一致)。

为什么 VGA 需要它?因为硬件不认识 Rust,只认内存里的一串字节。要让 Rust 的 struct 和硬件的寄存器/显存布局一致,必须保证:

1
2
3
4
5
Rust 眼中的 ScreenChar            VGA 眼中的一个字符单元
┌──────────┬──────────┐ ┌──────────┬──────────┐
│ ASCII │ Color │ === │ ASCII │ Color │
│ 1 byte │ 1 byte │ │ 1 byte │ 1 byte │
└──────────┴──────────┘ └──────────┴──────────┘

如果没有 repr(C),Rust 编译器有权重排字段或插入填充来优化布局——内存里长什么样是不保证的。对普通应用无所谓,但对映射到硬件的 struct 是致命的。同理,跨语言 FFI(调用 C 库)也要 repr(C) 保证两边看到同一份布局。

🔍 repr(transparent):单字段包装零开销

1
2
3
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
struct ColorCode(u8);

ColorCode 只有一个字段 u8,这种单字段包装常用来给一个基本类型起个有语义的名字。repr(transparent) 保证:ColorCode 的内存布局和这个唯一字段完全一致,不会在外面套一层多余的包装。

1
2
3
4
ColorCode ──► u8
┌──────────┐
│ color │ ← 一个字节的颜色属性
└──────────┘

用途:想给一个数组或基本类型起个有语义的名字,又不想引入任何布局开销时使用。也正因为这层零开销,VGA 的 ScreenChar 才能把 ColorCode 当成一个普通字节放进 #[repr(C)] 的布局里,整个 struct 保持整齐的 2 字节。

✅ 总结

属性是写给编译器的规则:derive 自动实现常用 trait,repr(C) 让 struct 布局与 C/硬件一致,repr(transparent) 保证单字段包装零开销。写 OS 时凡是要和硬件寄存器、显存或外部 ABI 对齐的 struct,基本都要用 repr 系列声明布局。

下一篇把这些语法全部串起来:逐行拆解 VGA Writer 的代码。

Happy Hacking! 🎉