一个写了近 30 年 C/C++ 的开发者,最近把话说得很重:没人能真正写出完全正确的 C 或 C++。

这不是外行唱衰。他说自己仍然喜欢 C++,听播客,看大会演讲,也承认这些语言撑起过现代软件世界。

反常点在这里:很多工程师以为 UB 主要是数组越界、use-after-free、double free 这种低级事故。但他列出的例子更刺眼。很多代码看起来像日常呼吸,一转身就是未定义行为。

发生了什么:UB 比你以为的更贴近日常代码

这次争论不该被读成“C/C++ 已死”。原文的重点更窄,也更难听:非平凡 C/C++ 代码几乎不可能靠个人经验彻底避开 UB。

几个事实先压缩清楚:

关注点原文信息现实含义
作者资历近 30 年日常写 C/C++批评来自语言内部,不是站外喊话
典型 UB未对齐访问、char 传 isxdigit、float 转 int、NULL/零地址、printf 可变参数类型不匹配、除零不全是菜鸟错误,很多藏在常见写法里
编译选项不开优化也不能免疫 UB问题不等于“优化器太激进”
LLM 角色作者用 LLM 找出自己代码和 OpenBSD find 中的问题,并提交补丁有用,但不能代替专家判断

最要紧的一句:UB 不是编译器看见漏洞后恶意搞你。

编译器只是按一个前提工作:你的代码是合法 C/C++。一旦这个前提不成立,后果就不再由程序员脑子里的“我本来想这样”决定。

未对齐读取就是典型例子。x86 可能宽容,SPARC 可能 SIGBUS,某些 Alpha 场景还会由内核帮忙模拟。代码没变,机器一换,命运就换了。

再看几个更日常的坑:

  • char 直接传给 isxdigit,在 signed char 架构上可能传入负数。
  • floatint,遇到不可表示范围或非有限值就踩线。
  • printf 里格式符和可变参数类型不匹配,不只是输出不好看。
  • 除零、NULL 或零地址相关假设,也不是所有平台都按直觉来。

这些问题麻烦在于:它们未必马上崩,也未必都能变成可利用漏洞。

但它们会让工程结果变得不可证明。对今天的软件组织来说,这已经够危险了。

真正的裂缝:语言抽象机、硬件和组织责任对不上

C/C++ 像老工业基础设施。桥还在,铁路还在,城市还靠它运转。可当年的载荷、车速、监管要求,都不是今天这套。

这个类比不完全一样。代码不是钢轨,修补成本也不同。但相似处很清楚:基础设施越老,越不能只靠“老师傅记得哪里不能踩”。

我不太买账“高手小心点就行”。高手当然能少犯错。但 C/C++ 把太多生存压力交给个人记忆力。

整数提升、对齐、别名、可变参数、空指针表示、浮点边界,随便拎一个,都够团队 code review 消耗半天。更现实的是,大多数团队没有那么多半天。

“天下熙熙,皆为利来。”放在工程组织里也成立。

业务要上线。安全要签字。审计要材料。老代码没人敢大改。于是 UB 最常见的命运不是被修掉,而是被标记成“目前没事”。

以前这可能只是技术洁癖。现在不是。

当 C/C++ 代码跑在支付、车机、医疗、云基础设施、身份系统里,问题就变了。它不再只是“程序员有没有写对”,而是“组织有没有能力证明自己没有把已知风险留给用户”。

这也是原文提到 SOX 合规时真正刺人的地方。不是说写 C++ 自动违法,而是当风险已知、工具可用、流程却继续装作不知道,责任就很难再推给某个粗心的人。

对 C/C++ 工程师,直接变化是工具链要前移。UBSan、静态分析、编译告警策略、关键路径的 LLM 辅助审查,都不该只在事故后出现。

对技术负责人,变化更硬:遗留代码不能只按功能排优先级。涉及外部输入、权限边界、序列化、文件解析、可变参数、跨平台构建的模块,要进入风险清单。

关注安全、合规和遗留系统治理的人,也要调整问题问法。不要只问“有没有漏洞”。更该问三件事:哪些 UB 类风险已知?谁复核?修不动时谁签字接受风险?

LLM 不是法官,但可以先当手电筒

作者把 LLM 指向自己的代码,又拿 OpenBSD 的 find 工具试了一下,发现了问题,并提交了一个越界写补丁。

这里要克制。这个案例不能被夸大成 OpenBSD 系统性崩盘,也不能证明 LLM 可以自动修好老代码库。原文也强调,几乎总要专家确认。

更准确的说法是:LLM 在 UB 审查上,可能已经足够便宜、足够勤快,适合做第一轮清扫。

它能提醒哪里可能有坑,解释标准边界,给出修法草案。最后那一步,仍然要懂 C/C++、懂平台、懂项目约束的人盯着看。

否则只是把历史债务换成 AI 垃圾补丁。

我更在意的是成本结构变了。过去找 UB 是专家苦活,太细、太脏、太不讨好,初级工程师又接不住。现在 LLM 至少能把“哪里该看”先圈出来。

它不是法官,更像手电筒。照出来不等于修好了,但不照,就只能继续靠运气。

接下来真正该观察的不是“有没有团队宣布迁移 Rust”。这太粗。

更该看四个动作:

  • 老 C/C++ 项目是否把 UB 审查写进 CI 或发布前检查。
  • 安全团队是否把 UB 风险和普通内存漏洞分层记录。
  • 技术负责人是否给高风险模块单独排修复预算。
  • LLM 生成的修复补丁是否有专家复核,而不是直接合入。

C/C++ 当然不会消失。既有代码库也不可能一夜迁走。

真正的分水岭是:还把 UB 当少数人的手滑,还是把它纳入安全、合规和工程治理的常规流程。

老基础设施可以继续用,但不能继续靠运气养护。