一个写了近 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 架构上可能传入负数。 float转int,遇到不可表示范围或非有限值就踩线。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 当少数人的手滑,还是把它纳入安全、合规和工程治理的常规流程。
老基础设施可以继续用,但不能继续靠运气养护。
