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 |
|
#[...]
不是函数调用,也不是注释,而是给编译器的指令:
下面这个东西,请按照某种特殊规则处理。
常见用途:#[derive(...)](自动实现
trait)、#[repr(...)](规定内存布局)、#[no_mangle](禁止符号名改写)、#——你在 Rust OS 项目里其实已经见过后面几个。
🪄 derive:让编译器自动生成功能
1 |
|
derive 的意思是让编译器自动给这个类型实现这些
trait:
1 | Debug 可以格式化打印 |
它给类型加功能,不改变数据本身。
🧬 repr(C):内存布局按 C 的规则
1 |
|
repr(C) 规定这个 struct 按 C
语言的内存布局规则排列(字段顺序、对齐方式都和 C 编译器一致)。
为什么 VGA 需要它?因为硬件不认识 Rust,只认内存里的一串字节。要让 Rust 的 struct 和硬件的寄存器/显存布局一致,必须保证:
1 | Rust 眼中的 ScreenChar VGA 眼中的一个字符单元 |
如果没有 repr(C),Rust
编译器有权重排字段或插入填充来优化布局——内存里长什么样是不保证的。对普通应用无所谓,但对映射到硬件的
struct 是致命的。同理,跨语言 FFI(调用 C 库)也要 repr(C)
保证两边看到同一份布局。
🔍 repr(transparent):单字段包装零开销
1 |
|
ColorCode 只有一个字段
u8,这种单字段包装常用来给一个基本类型起个有语义的名字。repr(transparent)
保证:ColorCode
的内存布局和这个唯一字段完全一致,不会在外面套一层多余的包装。
1 | ColorCode ──► u8 |
用途:想给一个数组或基本类型起个有语义的名字,又不想引入任何布局开销时使用。也正因为这层零开销,VGA
的 ScreenChar 才能把 ColorCode
当成一个普通字节放进 #[repr(C)] 的布局里,整个 struct
保持整齐的 2 字节。
✅ 总结
属性是写给编译器的规则:
derive自动实现常用 trait,repr(C)让 struct 布局与 C/硬件一致,repr(transparent)保证单字段包装零开销。写 OS 时凡是要和硬件寄存器、显存或外部 ABI 对齐的 struct,基本都要用repr系列声明布局。
下一篇把这些语法全部串起来:逐行拆解 VGA Writer
的代码。
Happy Hacking! 🎉