在编程圈,大家总是在效率和性能之间摇摆。一边是像 Python、Ruby 这样的高级语言,写起来简单高效;另一边是 C 这种硬核语言,性能强但开发成本高。按理说,现在都流行用高级语言,谁还会回头去写 C?
但有个开发者,干了 20 年 Ruby on Rails,又在 Common Lisp 里折腾了好几年,最后却一咬牙,彻底回归 C 语言。他不是追求复古,而是觉得虚拟机太耗资源,垃圾回收管不住,想要更好的性能和控制权。用他的话说,C 可能是唯一现实的选择。
我在法国的一所计算机学校里当了五年的好学生,也做了二十年的自由开发者。过去,我一直使用的是 Ruby on Rails 来为客户开发项目。
有一天,我开始学 Common Lisp,这是一种既能写函数式代码,也能写面向对象和过程式代码的多范式编程语言,属于 Lisp 家族。起初,我只是接了个短期任务,心想十天就能学会它,然后随便搞个服务器管理协议出来交差就行了。
结果,写着写着就有些“上头了”,我用 Common Lisp 写了一些临时代码,让它生成 C 代码,最后竟然做出了一个完整的 ASN.1 解析器和查询系统,用来支持一个专门的 Common Lisp 到 C 的 SNMP 服务器。
几年后,我写了越来越多的 Common Lisp 代码,最终开发出了 cl-unix-cybernetics,这是我所有 GitHub 仓库中 Star 数最多的项目。
此外,我还开发了 cl-streams、cffi-posix,以及 cl-facts,它是一个三元组存储系统,可用作 Common Lisp 图数据库。它的表现很不错:运行速度极快,支持原子事务和可嵌套事务,并兼容 unwind-protect。只需学习三个宏,就能上手使用。我在比利时的欧洲 Lisp 研讨会(ELS) 上做了一场演讲,介绍了 cl-facts,相关演讲幻灯片可在这里查看:https://git.kmx.io/facts-db/cl-facts/_tree/master/doc/facts.pdf。
开发这些 Common Lisp 包花费了很长时间,以至于作为自由开发者的我,逐渐失去了所有客户。但我并不在意,因为 Common Lisp 太棒了,我坚信它是未来一代开发者的重要工具。
然而,我也听到了不少质疑的声音,比如虚拟机本质上就是个资源黑洞,除了模拟环境,几乎是在白白浪费 CPU 和带宽。而 Linux 的容器(cgroups)也问题多多,远程命令执行(RCE)和权限提升(PE)漏洞层出不穷,每年都有新的被发现。我拿到的第一份安全报告里,就列了 10 多个 RCE 和 PE 漏洞,甚至有的能直接拿到远程 root 权限,连虚拟机都可能被攻破。至于 DevOps 领域那些工具,比如 Terraform 和 Ansible,也让很多人头疼不已。
与此同时,不少人对自己用的编程语言也充满怨言。拿 Clojure 举个例子:如果要做一个有上千个角色、每个角色都有自己视角的策略游戏,怎么避免垃圾回收器疯狂占用资源?事实上,我朋友试过用 Clojure 写这样的游戏,结果还是失败了——垃圾回收器实在是太拖后腿。而我的所有 Common Lisp 项目,也都受限于垃圾回收的性能问题。
大家都知道,JVM 的垃圾回收器已经是业内数一数二的,但光优化它就花了天价成本,这可不是一蹴而就的事。
于是,我开始思考:如果我开发了一款很棒的应用,但没人愿意用它,仅仅因为它是用 Common Lisp 写的,那该怎么办?如果要兼顾性能和可移植性,唯一合理的选择(除非特意开发一个新工具)就是 C。毕竟,Linux 是用 C 写的,OpenBSD 也是用 C 写的,GTK+ 是面向对象的纯 C,GNOME 也是 C,甚至大多数 Linux 桌面应用都是传统 C 语言编写的。
那我为什么还要纠结呢?C 语言我本来就会。
于是,我开始动手写 libc3 工具库。后来,它逐渐演变成了一门新语言——C3,并配套开发了一个解释器(ic3)。甚至在某个阶段,它有可能变成一门可编译的语言(c3c)。很快,基于 UTF-8 缓冲区的数据结构成型,并且所有操作都做了边界检查。尽管这样会增加一些内存消耗,但换来的稳定性和安全性值得。
我采用了防御性编程的理念,确保所有 Bug 在一开始就被扼杀。整个系统始终保持干净、无错,运行 KC3 代码不会引发任何安全问题。最终,一个小型解释器诞生了,它能在 REPL(读取-计算-打印循环)中处理各种数据类型的枚举标签(tagged union),运行流畅且稳定。
三年后,我完成了五层重构,所有测试再次通过,Web 服务器也正常运行了。由于原名称已被占用,C3 语言更名为 KC3。那么现在有什么成果呢?
https://kc3-lang.org/doc/1_KC3
我已经将 cl-facts(图数据库)移植到 C89,尽管导入时还有一些 Bug,但几乎整个数据库都是在 2020 年前完成的。
所有功能都齐全:添加/删除三元组、递归查询系统、事务处理、日志记录和持久化。它完美复现了我最初在 Common Lisp 版本中的设计,但全部使用 C89 实现,而我对 C89 记忆犹新。
除了图数据库,我还编写了各种解析器和生成器,为所有已知的算法类型提供形式化语义,并在短时间内实现了以下数据结构:结构体、链表、映射、哈希表、时间处理、复数、有理数、元组、代码块、引用/反引用、写时复制、跳表、集合 等。
我还设计了一些宏,我计划写一篇后续文章,展示一些宏的示例。
目前,我有一个交互式解释器 ikc3,它可以解析键盘或文件输入,并将 KC3 代码的计算结果输出到控制台(标准输出)。这在 KC3 的第二阶段单元测试中发挥了重要作用。我还开发了一个基于 MVC 框架的 Web 服务器 kc3_httpd,它正是用来生成你现在看到的这个网页。
此外,我的 Common Lisp 相关文章已经获得 700 次浏览,这让我很惊讶——没想到 Common Lisp 还有这么多关注者。
最后,我还构建了一个文档网站(https://kc3-lang.org/doc/1_KC3),整个网站都由 kc3_httpd 运行,并基于一个 Markdown 到 HTML 的 C 实现。
整体而言,KC3 的设计灵感来自 C、Elixir 和 Common Lisp,可以被描述为在 C 的基础上,融合了 Elixir 的模块、模式匹配和语义对象系统。以上就是我重新开始写 C 语言的主要原因。