默认有符号整数

Lobsters Hottest 新闻

摘要

一篇讨论编程语言设计中默认使用有符号整数还是无符号整数的文章,以 Odin 和 C3 为例。作者认为尽管无符号整数在理论上有优势,但默认使用有符号整数更实用。

<p><a href="https://lobste.rs/s/asnoqy/signed_integers_by_default">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/07/07 12:16

# 默认有符号阵营 来源: https://www.gingerbill.org/article/2026/05/03/signed-by-default/ 2026-05-03 与编程领域的许多讨论一样,不同做事方式之间也存在“战争”。这些通常涉及微小的美学偏好,例如: - 制表符 vs 空格用于缩进 - `snake_case` vs `camelCase` vs `Ada_Case`用于命名约定 - 字符串使用`'单引号'` vs `"双引号"`(如果语言两者都允许) - 1TBS vs K&R vs Allman 用于`{}`花括号风格 这些战争大多毫无意义;真正重要的是编码风格的一致性和连贯性。然而,在设计语言时,一些二选一的选择会产生巨大影响。本文重点讨论这样一个选择:整数默认是有符号还是无符号。 同为语言设计师的 Christoffer Lernö(C3 语言,https://c3-lang.org/)撰写了一篇文章,讲述他决定将 C3 的默认整数类型从无符号改为有符号:无符号大小:五年的错误(https://c3-lang.org/blog/unsigned-sizes-a-five-year-mistake/)。我强烈推荐这篇文章,因为它涵盖了发现过程以及在设计编程语言时必须做出的权衡。 ## 不同阵营 在设计 Odin(https://odin-lang.org/)时,我明确选择了默认有符号。早在开始设计 Odin 之前的许多年,我曾属于默认无符号阵营,但我见过太多人在无符号算术中犯错误。有人可能认为无符号“理论上”更好,但有符号“实践中”更有效。就我个人而言,如果某样东西在实践中不符合“理论”,那么该“理论”从一开始就是错误的,只是一个虚假假设。 最常见的问题是使用无符号类型来“强制”保证值永远不会为负。讽刺的是,这些人做算术时假设普通代数规则适用,认为子表达式(例如 `a-b+c` 中的 `a-b` 部分)即使最终结果为正,也不会变为负数。这会导致无限循环和越界错误。“永不值为负”的心态是对机器上整数工作方式的根本误解。你可以称之为技能问题,但这种现象如此普遍,以至于我认为这样否定并不公平。 我的同事属于几乎总是无符号(https://graphitemaster.github.io/aau/)我也强烈推荐阅读这篇文章,以了解有符号整数的边界情况和病态情况。阵营。我与他在这个话题上的最大分歧是,坚持该阵营需要对每一个操作都**高度**熟练和谨慎,即使是最熟练的程序员也并非总能如此小心。他知道所有边界情况和病态情况,但我认为大多数默认无符号的倡导者实际上并不了解。而那些真正了解的人,恰恰是默认整数类型对他们影响最小的人。 我还定义了 Odin 中所有整数算术(包括有符号和无符号)都是回绕的,并定义了诸如 `x / 0` 之类的操作结果用户可以在编译器级别或文件级别定义除以零的行为(陷阱、零、自身或全位集)。默认是陷阱,但有些情况下定义除以零是有益的,尤其是在证明算法中。以及 `INT_MAX - INT_MIN`,使其不再是“未定义/非法行为”。 最后,语言是否支持隐式数值转换也很重要。许多语言仍然执行隐式整数转换,尤其是在没有丢失**值**信息的情况下。在 Odin 中,即使如此我也禁止了这一点,原因有很多,但一个主要原因是即使**值**被保留,**类型**信息也会丢失。使所有转换成为显式转换可以使意图更清晰,并让程序员做出选择。正如本文所指出的,C 语言中许多无符号错误源于有符号和无符号之间的隐式转换。语言设计应该考虑到过去语言的错误,并理解它们产生的背景,而不是全盘否定整个思想。 ## 可能的隐式转换规则 隐式整数转换规则可以很复杂,也可以非常简单,不同的规则集导致不同的结果。以下示例: `` a: u8 = 53 b: u8 = 34 c: u16 = a * b `` 问题是:`a * b` 会发生什么?可能的方法如下: 1. 无隐式转换(Odin 风格) 2. 朴素的单向(自下而上)类型转换 3. 转换为“自然”整数大小,然后隐式截断(C 风格) 4. 转换为最大整数大小,然后隐式截断 5. 双向(自上而下)类型转换 选项 0 使其成为类型错误,并迫使用户指定预期行为。选项 1在这些方法中,我认为选项 1 是设计语言时客观上更差的选择。如果希望保留值信息,它会在实践中导致最多错误,并且不允许任何允许宽度扩展的性能优化。要么回绕到 8 位(即使 `53 * 34` 适合 16 位),要么将溢出视为非法或陷阱。选项 2 类似于 C 的做法:操作变为 `u16(u32(a) * u32(b))`,保留最多的值信息,并且使用自然整数大小可能性能更好。选项 3 是同样的思路,但使用更大的中间类型,如 `u64` 或 `u128`。选项 4 是我如果需要隐式转换的首选方法:从左侧的左手侧类型自上而下传播类型提示,使其作为语法树中最宽的类型,直接得到 `u16(a) * u16(b)`。 ## 上溢/下溢,无忧无虑 有些语言(如 Rust)在调试构建中将溢出算术视为陷阱(代价高昂的运行时开销),而在发布构建中则视为回绕。我不喜欢行为随优化级别改变;我希望事物行为一致,除非我明确要求不同。 另一个问题是:当发生上溢或下溢时,哪种情况更糟糕?对于有符号和无符号整数,我认为上溢远不如值低于零常见。正因为如此,无符号整数的下溢是更常见的错误,并且比仅仅出现负值更可能导致严重问题。

相似文章

C语言中无法解析整数 (2022)

Hacker News Top

文章批评了C标准库中用于解析整数的函数(atol、strtol、strtoul、sscanf),解释了为什么大部分函数存在缺陷,只有strtol在仔细进行错误处理的情况下才能正确使用。

数据的顺序:默认值、性能、确定性与分页

Lobsters Hottest

本文解释了SQL查询中不使用ORDER BY时未定义的默认排序行为,通过PostgreSQL示例展示了插入顺序、更新和删除如何改变结果,并警告不要依赖默认排序进行分页。

值编号

Hacker News Top

本文解释了值编号,一种编译器优化技术,用于识别相同的计算以避免冗余,基于静态单赋值(SSA)形式,并使用哈希合并进行高效比较。

John Regehr 的 C 语言整数测验

Lobsters Hottest

这是 John Regehr 的 C 语言整数测验的存档版本,使用 JavaScript 封装以实现浏览器兼容性,重点介绍了 C 代码中棘手的未定义行为和整数问题。