编程与软件工程queue.acm.org
最昂贵的一字节错误
The Most Expensive One-byte Mistake
2011 — 2018,它一次次回到首页
黑点是上首页(≥ 50 分)的那次投稿,空心点是没冲上去的投稿,点越大分数越高。
导读
这篇 ACM Queue 文章认为 C 语言选择以 NUL 结尾的字符串(而非带长度前缀)是历史上代价最高的“一个字节”错误,由此带来的缓冲区溢出和安全漏洞影响至今。文章引发了对这一设计在 1970 年代是否合理的激烈争论。
为什么经久不衰:它把一个看似微小的技术决策与几十年的安全漏洞联系起来,促使人们反思技术债与历史约束,至今仍是讨论字符串设计的经典参照。
HN 评论精选
NUL 结尾让后缀即字符串,避免昂贵的内存复制,符合当年的资源约束
用 NUL 结尾字符串还有另一个理由:字符串的后缀本身也是字符串。想把 "hello world" 拆成 "hello" 和 "world"?只需把空格替换成 '\0'。而长度前缀字符串就得把 "world" 复制到新分配的内存里——对 1970 年代的编程来说太浪费了。
cperciva查看英文原文
There's another reason for using NUL-terminated strings: It means that a suffix of a string is also a string. Want to convert "hello world" into "hello" and "world"? Just replace the ' ' with a '\0'. With length-prefix strings, you would need to copy the "world" into a new memory allocation -- which is far too wasteful for 1970s programming.
从简单性、字节序、原地解析等角度论证 NUL 结尾是 C 语境下的合理选择
NUL 结尾字符串对 C 来说是正确的决定,比长度字段简单得多。长度字段该多大?固定大小会带来可表示字符串长度的限制和跨架构差异;变长编码则又引入一堆复杂度,需要额外的库函数来读写长度,很不符合 C 的精神。还有字节序问题:NUL 结尾字符串没有字节序问题,可以随意写入文件或网络包。strtok/strsep 这类原地解析也无法用长度字段字符串实现。显式长度在有强制抽象(如 std::string)时更好,但那已经不是在写 C 了。
millstone查看英文原文
NUL terminated strings were the right decision for C. They’re certainly much simpler than length fields. Consider using a length field. How big should that field be? If it's fixed size, you introduce complications regarding how big a string you can represent, and differences in field sizes across architectures. If it's variable-sized (a la UTF-8), then you've added different complications: you would need library functions to read and write the length, to get access to the string contents, to calculate the amount of memory required to hold a string of a given size, etc. Very much not in the spirit of C. Next, what endianness should that field have? NUL terminated strings have no endianness issues: they can be trivially written to files, embedded in network packets, whatever. But with a length field, we either need to remember to marshall the string, or allow for the length field to not be in native byte order. Neither is a pleasant prospect, especially for a 1970’s C programmer. Also, consider C-style string parsing, e.g. strtok/strsep. These could not be implemented with length-field strings. Explicit length is better when you have an enforced abstraction, like std::string, but at that point you’re not writing in C. If you have to pick an exposed representation, NUL termination is much better than Pascal-style length fields. So what was the “one-byte mistake?” The article says that it was saving a byte by using NUL termination instead of a two-byte length field. Had K&R not made that “mistake,” we would be unable to make a string longer than 65k - a far more serious limitation than anything NUL termination imposes! K&R got it right.
当年省字节是正确决策,真正的问题是从未回头修正
很多编程决策都是为了省下一个字节。今天很容易指责它们“糟糕”,但在当时那是绝对正确的做法——不省那个字节,程序可能就装不进内存。你试试在 1960 年代告诉管理层程序因为“编码规范”而无法加载,看能走多远。我们真正失败的是从未回头审视这些决策并修正已发现的问题。设计并维护一套 libc 替代品是巨大的工程,不会随随便便发生。所以就有了今天这个局面。
TomMasz查看英文原文
A lot of programming decisions were made to save a byte here and there. It's easy to point at them today and say they're "bad", but at the time they were the absolutely correct thing to do. It's hard to imagine now but not saving that byte could mean your program wouldn't fit into RAM. Try telling your management in the 1960s that your program won't load because it's "properly coded" and see how far you get. What we've failed to do is ever revisit those decisions and change them where we've identified problems. Yes, you can probably compile (with warnings) files from UNIX v7, but we pay for that compatibility. But there's no question designing, building and maintaining a libc alternative is a colossal undertaking and not likely to happen on a whim. So here we are.
一个字节的说法不成立,且 C 的长寿反而说明其选择可能是对的
文章说只差一个字节,这显然不对,因为我们总需要超过 255 字符的字符串。实际哪怕短期内也至少需要两个字节,而那仍然很受限。NUL 结尾字符串让你不必操心长度字段的宽度和存储时的字节序。他还提到同时代其他语言没用 NUL 结尾字符串——但考虑到 C 至今仍比同时代语言用得广泛,也可以解读为:做了不同选择的 C 恰恰做对了。
supersillyus查看英文原文
In the article he says it is just one byte extra, but that's clearly not the case, since we probably want strings longer than 255 characters. To be practical, even in the sort term, you'd probably need at least two bytes, and that's still a bit limiting. NUL-terminated strings keep you from having to worry about the size of the length specifier and what byte ordering when storing it. He notes that other languages of the day didn't go with NUL-terminated strings. It could also be interpreted, considering that C is more widely used still than other languages from the time, that by doing something different, C made the right choice.
历次讨论
同类还值得读
开源应用架构
The Architecture of Open Source Applications
《开源软件架构》(AOSA)是一本在线免费书,邀请知名开源项目的作者亲自剖析其内部设计,涵盖 SQLAlchemy、LLVM、Audacity、Apache、Bash 等系统。
编程与软件工程上首页 9 次 · 2011–2024aosabook.org编辑精选编程语言简史:简短、不完整且多半错误
A Brief, Incomplete, and Mostly Wrong History of Programming Languages
一篇以戏谑、故意不准确的方式回顾编程语言史的博客文章,用夸张轶事和讽刺定义串起 Lisp、C、C++、Java、JavaScript、PHP 等语言。
编程与软件工程上首页 8 次 · 2009–2021james-iry.blogspot.comSQLite 是如何测试的
How SQLite is tested
SQLite 官方文档页,系统介绍 SQLite 的测试体系:多种测试方法(TCL 测试、模糊测试、回归测试、静态分析等)和海量测试用例,展示了这个小型开源项目如何靠极致测试获得…
编程与软件工程上首页 8 次 · 2009–2025sqlite.org编辑精选高级编程语言(2009)
Advanced programming languages (2009)
Matt Might 的经典文章,按用途推荐“高级编程语言”:如果追求就业就选主流语言,如果追求表达力、学术研究或创业,则推荐 Haskell、OCaml、Scala 等函数式语…
编程与软件工程上首页 8 次 · 2009–2022matt.might.net我的 Forth 与栈机器经历
Yosefk - My history with Forth & stack machines
作者以亲身经历回顾了使用 Forth 语言和栈式机器的历程:既赞叹其简洁优雅、代码量小、可自举的优点,也坦率指出栈操作在实际编程中的痛苦,以及 Forth 社区文化中的问题。
编程与软件工程上首页 8 次 · 2010–2024yosefk.com编辑精选