《内存安全魔典,第一部分》摘要
本文是作者Evan Ovadia关于内存安全技术的系列文章引言,通过一个虚构的考古探险故事作为引入,系统性地梳理了他所总结的14种(实际列出了17种)内存安全技术方法。其核心观点是,内存安全并非仅有垃圾回收(GC)、引用计数(RC)和借用检查这几种主流方案,而是一个广阔且尚未被充分探索的领域,各种技术之间可以相互混合,创造出新的范式。
背景与目的
作者自称收藏了一本“内存安全魔典”,其中记载了多种内存安全技术。本文旨在公开这份清单,展示内存安全领域的多样性,并启发开发者探索新的混合技术,以设计出更优的编程语言。
传统技术的局限与混合可能性
文章首先简述了三种传统技术的特点与不足:
- 垃圾回收(GC):简单灵活、吞吐量高,但内存能耗大、有非确定性暂停。
- 引用计数(RC):简单、内存占用少,但速度慢且可能因循环引用导致泄漏。
- 借用检查:速度快、可安全地使用内联数据,但可能导致代码复杂性,且难以支持观察者模式、侵入式数据结构等模式。
作者认为,玛雅古人(故事中的隐喻)可能掌握了更底层混合这些技术的方法,以取长补短。
17种内存安全技术清单
文章核心部分逐一简要介绍了以下技术,多数技术都在寻求混合或改进:
- 仅移动编程:对象一次只能有一个所有者,所有权可转移。它是仿射类型和线性类型的基础,Rust在此之上增加了借用检查。
- 引用计数:可与GC共存(如Python),并能与不可变区域借用结合以减少缓存未命中和数据竞争。
- 借用检查:确保临时、受限地使用指针。Austral语言通过结合线性类型使其不仅安全,而且正确(保证某些未来动作会发生)。
- 纯竞技场编程:仅使用竞技场分配内存,不单独调用malloc/free。通过跟踪指针所属竞技场可防止释放后使用错误。
- Ada/SPARK的作用域限制:禁止指针指向比自身作用域更深的对象,类似限制性借用检查。
- 区域:强大且灵活,可定义对象间的隔离子图或临时打开的不可变视图,能极大优化引用计数和生成式引用的性能。
- 栈竞技场:自动为每个栈帧分配竞技场,但目前效率不高,可与其他技术结合提速。
- 生成式引用:通过比较指针中的“记忆代数”与目标对象中的“当前代数”来防止释放后使用。结合区域和纯函数可消除比较开销。
- 随机生成式引用:生成式引用的快速变体,支持内联数据,性能潜力更高,兼具C++的架构简洁性和Rust的内存安全性。
- MMM++:对象从全局数组分配,槽位按类型复用,避免传统释放后使用问题,常见于嵌入式和实时系统。
- 追踪式垃圾回收:可通过分离执行单元(如Pony的Actor)避免全局停顿,结合区域和约束可进一步控制。
- 交互网络:一种高效管理纯不可变数据的方式,无需GC或RC,如HVM运行时所示,本质上是自动借用和克隆的混合。
- 约束引用:混合了引用计数和单一所有权。对象有单一所有者和引用计数,销毁时需断言无其他引用。支持比借用检查更多的模式(如侵入式结构、图、观察者等),但检查发生在运行时。
- 线性引用计数:一个设想中的概念,旨在编译时消除计数整数,通过线性类型在编译期完成引用计数。
- 非MVS:类似只有唯一引用(&mut)而无共享引用(&)的Rust,简单快速,但可能需要更多克隆。
- CHERI:硬件-软件混合方案,指针包含地址范围和权限,实现空间安全。结合特定分配器(如Cornucopia)可增加时间安全。
- 永不释放:通过永不调用free来避免释放后使用。适用于生命周期短暂或可接受内存增长的程序(如导弹制导软件)。
其他构建模块与思考
文章末尾列举了更多可用于设计内存安全模型的技巧或概念,如类型稳定性(真正的问题是类型混淆后的访问)、唯一引用、变更检测器、线程隔离、胖指针、页眉、高位忽略等。
结论与展望
作者总结道,内存安全是一个广阔的开放世界。每种范式(如RC、GC、借用检查、线性类型)都有其独特优势,且存在大量可能的混合方式。他鼓励开发者不要陷入“一切问题都已解决”的思维定式,应积极探索新的技术和混合方案,这可能会催生出如Vale语言中的完美可重放性等意外特性,并最终在服务器等领域产生更广泛使用的范式。
(注:文中将玛雅探险作为技术隐喻,部分技术名称如“MMM++”等为作者自拟或待规范化。)