直接回答:C 的严格别名规则按类型划分(int* 与 float* 不许互指),而 Rust 的别名规则按引用种类划分且更强:&mut T 存续期间必须独占,&T 存续期间所指内存不许被修改。把 &mut 强转成裸指针再并发地造出两个可写路径,哪怕全程用裸指针,也违反规则——UB 由“来源引用”的契约决定,不是裸指针就豁免。

展开解析:Stacked Borrows 是目前最具体的操作语义模型:每块内存维护一个借用栈,从 &mut 派生裸指针相当于压栈一项,通过旧指针访问会把栈顶之上的新项弹掉使其失效。实战含义:&mut *p 再从 p 读可能 UB——新引用把旧指针挤下栈顶;两个 &mut 分别转 *mut 再交叉写必然 UB;正确的裸指针模式是“单一来源”——整块内存先拿到一个 *mut(如 Box::into_raw、as_mut_ptr),后续所有访问都从它线性派生(wrapping_add、byte_offset),绝不中途回头制造新的引用。与 C 的另一差异:Rust 编译器基于 noalias 做激进优化,违法别名不保证“碰巧能跑”——LLVM 会按独占假设重排删除你的读写。调试手段:Miri 默认按 Stacked Borrows 检查,Unsafe 代码过 Miri 是底线;Tree Borrows 是放宽中的后继模型,对transmute 出来的指针更宽容。工程守则:能偏移运算就不造引用,必须造引用就让它的生命周期最短,引用与裸指针的活跃期绝不重叠。

追问方向:为什么 self-referential 结构几乎无法安全手写?as_ptr 与 &mut 混用的安全顺序是什么?

(约 490 字)