Linux 在历经六年、360 个补丁后移除了 strncpy API
空终止字符串 vs 长度前缀 / “真正的”字符串类型
- 许多人认为 NUL 终止的 C 字符串是计算机领域最糟糕的设计选择之一:很容易溢出、难以推理,而且有性能陷阱(例如反复调用
strlen会把 O(N) 变成 O(N²))。 - 也有人指出,在 20 世纪 70 年代那些资源极少的小机器上,这是一种务实的折中;以哨兵终止的序列(字符串、指针数组、链表)成本低,而且与汇编实践相符。
- Pascal 风格或长度前缀字符串可以避免遗漏终止符的 bug,但也带来问题:长度字段固定大小(历史上通常是 255 个字符)、字节/代码点/字形之间的混淆,以及如果不转向“胖指针”(指针+长度),就很难在不复制的情况下做子串。
- 讨论中提到的现代变体包括:D 的
struct { size_t length; T* ptr; }、BSTR 以及 Pascal/Delphi/Free Pascal 的头部、动态/可变长度的长度编码,以及 “span”/切片类型。权衡点在于:内存开销 vs 速度 vs 零拷贝切片。
NULL、NUL 与 Option 类型
- 讨论反复区分了 NUL(字节 0 终止符)与 NULL(无效指针)。
- 有人认为 NULL 没问题且是必要的;真正的问题是不允许声明非空指针。
- 也有人强调现代的 “option”/和类型(例如
Option<T>)才是表示“未设置”值的正确方式,由类型系统在编译期强制保证。 - 争论点在于:低层环境是否能或是否应该用这类结构替换 NULL,还是把它们作为更高层的抽象,再编译成可为空的表示。
历史与标准背景
- 一些评论强调 C 的起源背景:极少的 RAM(几十 KiB)、单遍编译器、PDP-11 的寻址限制,以及对既有惯用法的复用。在那个背景下,空终止字符串和指针/数组退化被视为巧妙的折中,而不是错误。
- 后来的提案,如胖指针/切片和更安全的 API(例如
strlcpy),被视为错失的机会;有人批评标准委员会加入了复杂特性(VLA、_Generic),却让 C 的核心字符串模型和标准库基本冻结不变。
strncpy、内核 API 与安全性
strncpy被描述为被广泛误用且反直觉:它会用零填充,在截断时可能不会 NUL 终止,而它最初是为固定大小、带填充的字段(例如旧目录项)设计的,并不是“安全版strcpy”。- 内核的替代函数(
strscpy、strscpy_pad、strtomem_pad、memcpy_and_pad、memcpy)更清楚地表达了意图(是否终止、是否填充、还是原始拷贝),代价是 API 更多,但安全性和性能意图更明确。 - 有人觉得这种增殖“过于复杂”;也有人认为一个“瑞士军刀”式函数会更慢,并且会模糊意图。
AI 与自动化重构
- 一条讨论线索在问,基于 LLM 的编码器是否本可以自动完成大部分
strncpy移除工作。 - 支持者分享了用 LLM 重构许多 C 程序的良好经验,并认为六年对于本质上是机械性的改动来说太久了。
- 怀疑者则反驳说,在内核规模的项目中,主要瓶颈是审查、协调和测试,而不只是编辑;必须仔细审计细微的下游行为和对旧语义的依赖。
- 讨论中对 AI 被注入到每一场编程讨论里的现象可见疲惫,同时也对它在大规模重构中的潜力抱有好奇。