实用内存安全
摘要
文章讨论了最近通过 Fil-C 提出的 Zig 内存安全方案,比较了 Fil-C 与 Rust 对内存安全的定义,并主张内存安全是一个具有实用定义的频谱,同时使用了一个 Fil-C 仍然不安全的具体示例。
<p><a href="https://lobste.rs/s/yjzexe/practical_memory_safety">评论</a></p>
查看缓存全文
缓存时间: 2026/08/03 09:32
# 实用的内存安全
来源:https://ohadravid.github.io/posts/2026-08-unsafe-water/
最近:
1. Zig 正在考虑(https://codeberg.org/ziglang/zig/issues/36237)一个安全的、类似 Fil-C(https://fil-c.org/)的编译目标。
2. Fil-C 是“一种内存安全的 C 实现”,Zig 的 issue 声称这将“真正做到内存安全”。
3. 这被表述为“不像 Rust”,因为 Rust 有逃生舱,也就是 Unsafe Rust(https://doc.rust-lang.org/book/ch20-01-unsafe-rust.html)。
4. 但事实证明,Fil-C 对内存安全的定义(https://fil-c.org/#:~:text=Memory%20Safety%3A%20Advanced%20runtime%20checks%20to%20prevent%20exploitable%20memory%20safety%20errors.)与 Rust 的定义不同。
5. 所以也许 Fil-C 同样是不安全的(https://ohadravid.github.io/posts/2026-08-unsafe-water/#unsafe-fil-c)?
你可能会看着这一切,心里想:*我可没空读那么多*。而且你可能会得到一种*感觉*:内存安全是某种模糊且有争议的东西[1](https://ohadravid.github.io/posts/2026-08-unsafe-water/#fn:1),没有对错之分……所以也许你应该*少*关心它?
但事实并非如此!完全不是!在读完这篇文章时,我希望说服你接受两个看似矛盾的观点:
1. 内存安全*可以*被视为一个光谱。我们可以拥有*更多*的内存安全——即使细节可能微妙且令人沮丧,这也是好事。
2. 可能存在一个*实用*的内存安全定义,它与细微差别完全无关——这也很好,尤其是**对你**而言。
那么,让我们深入探讨,首先从最近这些喧嚣的要点开始 ☝️
## 非安全的 Fil-C
改编自这条 lobste.rs 评论(https://lobste.rs/c/ay4ynr),这里有一些 C 代码:
```
struct User {
char name[8];
int is_root;
};
struct User* user = malloc(sizeof(struct User));
strcpy(user->name, argv[1]);
```
这段代码分配了一个 `User`,并从第一个命令行参数复制一个字符串。
但是当输入长度达到 8 个字符或更长时会发生什么?
```
if (user->is_root) {
printf("I am root!\n");
} else {
printf("I am not root :(\n");
}
```
这段代码可能会打印 `I am root!`,即使在 Fil-C 中也是如此,因为 `strcpy` 会愉快地覆盖你拥有的内存。Fil-C 将内存安全定义为“运行时检查以防止可利用的内存安全错误”。上述代码在*技术上*并不可“利用”,所以这种覆盖是被允许的。
如果你*超出分配范围*写入,这段代码*确实*会触发陷阱[2](https://ohadravid.github.io/posts/2026-08-unsafe-water/#fn:2),所以提议的 Zig 模式**绝对会比 Yolo-Zig**[3](https://ohadravid.github.io/posts/2026-08-unsafe-water/#fn:3) 更安全,这是一件好事。另见 steveklabnik 在 HN 上的评论(https://news.ycombinator.com/item?id=49087952)。
显然,如果 Fil-C 让这段代码*更*内存安全,那么整体上的内存安全必然是一个光谱!
但你会认为这是“一个漏洞”吗?你会认为这段代码是“安全”的吗?
这些都是大问题,虽然我喜欢技术正确的答案,但我想为“什么是内存安全”这个问题贡献一个*少些*微妙的答案,我认为这个答案更实用:
内存安全的代码是**其中“内存”拥有 (1) 直观且 (2) 易于遵循的规则**的代码,**除非有一个巨大的标志标明“否则”**。
在我脑海中,这也是“无浮力水的内存安全法则”。
危险标志:警告水没有浮力
来源:Reddit(https://www.reddit.com/r/thalassophobia/comments/9r921s/theres_something_particularly_terrifying_about/)
另见:“Is NON-BUOYANT WATER Deadly?”、YouTube(https://www.youtube.com/watch?v=ey06E4iEXzg)
显然,这个定义对于那些真正设计内存模型、编译器和 CPU 的专家来说并不适用——他们喜欢做一些有趣而重要的事情,比如*证明*程序的性质,消除无法被观测的操作,等等。
所以更准确地说:拥有一个恰当的*技术性*内存安全定义**很重要**——因为它确保了内存上的*安全*操作符合我们对内存的一般预期,并且其规则易于正确遵循。
边界的不安全一侧可以拥有复杂甚至晦涩的规则,但如果安全一侧*也*拥有晦涩且难以遵循的规则,那么画出这条边界*还有用吗*?
简单且自动执行的规则比复杂且未检查的规则更容易被正确遵循。[4](https://ohadravid.github.io/posts/2026-08-unsafe-water/#fn:4) 但是,如果每次内存访问都带有一个星号,那么“安全”这个词还有什么意义呢?
因此,即使 Fil-C*确实*比 Yolo-C 更安全,提议的“内存陷阱 Zig”*确实*比 Yolo-Zig 更安全,但它们并非真正意义上的安全,至少在**对你**唯一重要的意义上不是。
它们仍然拥有未标记且未检查的内存“脚枪”(footgun),需要**你**去检查并时刻保持警惕。称其为“内存安全”多少有点贬低了人们所热捧(并期待!)的“内存安全”这一称号。
## 结论
内存安全很棒!它**为你**服务:它让其他人——编译器作者或标准库设计者——的生活更艰难,目的是让**你**能够更好地处理你关心的事情!
定义中可能存在一些微妙之处,当然也有一些与内存安全无关的问题值得系统性解决,但拥有内存安全的目的是让整体上的微妙之处*更少*。
或者借用 Ralf Jung 的说法(https://www.ralfj.de/blog/2019/07/14/uninit.html#:~:text=When%20writing%20safe%20Rust%2C%20you%20do%20not%20have%20to%20worry%20about%20this),略有偏离语境:“在编写安全 Rust 时,你无需担心”抽象机器。事实上,世界已经提供了足够多值得担忧的事情,不需要再把这件加上去。
## 附录:更多示例
### Python CFFI
Python 是内存安全的:你可以使用 CFFI 来做任何你想做的事:
```
from cffi import FFI
ffibuilder = FFI()
ffibuilder.cdef("...")
# Or
from ctypes import *
cdll.LoadLibrary("libc.so.6")
libc = CDLL("libc.so.6")
```
但每个东西的字面名字都带个 C。这就是标志。
类似地,任何将 FFI 或不安全内存访问作为显式构造的语言(Rust 中的 `unsafe extern "C"`,.Net 中的 P/Invoke 和 `unsafe`,或 Java 的 JNI)*可以*是安全的。
### Rust 的 `/proc/self/mem`
一种语言并*不*仅仅因为它允许你像这样写入内存就是非安全的:
```
let mut f = fs::OpenOptions::new()
.write(true)
.open("/proc/self/mem").unwrap();
```
是的,你可以用这个犯下非常严重的内存罪行。然而,这里有一个明显的标志:`/proc/self/mem` 是一种很好的表达“所有约定全部失效”的方式。
### Zig 是非安全的
这并不存在争议,但可以参见之前的一篇文章(https://ohadravid.github.io/posts/2025-05-zig-mem-safety/)。
相似文章
内存安全绝对主义者
本文批评了编程语言辩论中的内存安全绝对主义,认为像 Fil-C 这样的新方法也有权衡,而将 Rust 视为不安全忽略了实际好处。
内存安全是生死攸关的问题
作者认为,内存不安全的开源软件极易受到即将到来的人工智能漏洞查找代理的攻击,这使内存安全成为道德义务,并且Rust必须作为领先且零开销的内存安全语言取得成功。
Fil-C: Garbage In, Memory Safety Out
Fil-C 是一个完全内存安全的 C/C++ 实现,通过 LLVM IR 阶段的不可见能力(invisicaps)将指针值与边界信息捆绑,实现高兼容性,性能损失约 4 倍。
安全变得简单 第1部分:单一所有权(并非)可选
本文介绍了一种基于线性类型和抽象解释的内存安全新方法,旨在比Rust更符合人机工程学原理地消除诸如释放后使用和内存泄漏等常见错误。
Rust与C/C++在内存安全CVE上的差异
分析Rust与C/C++在内存安全CVE报告方式上的不同,论证即使存在错误,Rust的设计也能降低某些类型漏洞的发生。