返回

文章详情

几乎总是无符号的

Hacker News2026年7月8日 19:35

作者: Dale Weiler Twitter GitHub 最后更新: 2022年1月1日 在程序中,将整数表达为符号整数的需求通常是错误的,因为大多数整数从未表示负值。数组的索引和循环的迭代计数也反映了这个概念。应该倾向于更常使用无符号整数,但尽管如此,大多数代码几乎专门选择使用符号整数。这篇文章的大部分动机适用于C和C++,但也将提供诸如Go、Rust和Odin等其他语言的示例,以尝试表明这一概念适用于所有语言,无论它们的选择如何(例如C和C++将符号整数的溢出行为定义为未定义),而是与算术本身的内在性质有关。 反对无符号的论点 # 对使用无符号整数有很多反对的论点。让我解释一下我认为这些论点大多是错误的原因。 安全性论点 # 反对使用无符号整数的最典型论点是,因表达式更容易发生下溢而更易出错。这种建议如此普遍,以至于谷歌官方C++风格指南明确不鼓励使用无符号类型。我们将在接下来的论点中看到这些安全问题来自何处,以及如何通过简单的模式轻松避免它们,这些模式比到处使用符号类型更容易理解。我们还将看到这些论点在大多数情况下是错误的,因为它们鼓励继续编写和使用不安全的代码。 反向循环论点 # 当for循环的计数器需要反向计数且循环体需要在计数器为零时执行时,大多数程序员会发现无符号使用起来很困难,因为i >= 0总是评估为true。诱惑是将无符号值强制转换为符号值,例如: for ( int64_t i = int64_t (size) - 1 ; i >= 0 ; i -- ) { // ... } 当然,这是危险的,因为它是窄转换,并且强制转换会消除有效的警告。在C和C++中,当给定特定的大值时,它会引发未定义行为,并且无疑可以被利用。大多数应用程序在输入>= 0x7ffffffffffffffff时只能崩溃。典型的论点是这样的值是“病态的”。这个论点不仅错误,而且更加危险,我们稍后将看到。这个危险是总是使用符号整数算术的理由之一。然而,这个论点本身是错误的,因为int64_t绝对不会允许大于或等于0x7ffffffffffffffff的值。它只是避免了该限制以上特定问题数字范围的问题。如果你需要这么大的值,而如果你遵循谷歌的明智建议始终使用符号且具有这么大的值,那么你就会遇到一个明显更糟糕的问题,因为你现在无条件引发符号溢出。对像C和C++这样的语言来说,这引发未定义行为。而像Go和Odin这样的语言则会因为那种包装行为而在循环中出现错误的数字范围。 这里的正确方法是无符号下溢在C和C++中是明确的,我们应该教授包装算术的行为,因为它在一般情况下是有用的,但它也使反向迭代变得同样简单。 for (size_t i = size - 1 ; i < size; i -- ) { // ... } 这里的方法是从size - 1开始,在每次迭代中向下计数。当计数器达到零时,递减会导致计数器下溢,并循环回无符号类型的最大可能值。这个值远大于size,因此条件i < size评估为false,循环停止。像Rust这样的语言选择将无符号下溢在调试构建中处理为失败表示,但特定特性如范围将让你安全地在下溢时实现相同的高效包装行为,使用更简洁的语法。 for i in (0 .. size).rev() { // ... } 通过这种方法,不需要强制转换,不引入隐性错误,并且“病态”输入仍然可以正常工作。事实上,这种形式允许来自[0, 0xffffffffffffffff)的每个可能值,覆盖了整个范围。应该注意的是,如果size == 0,这些循环仍然有效,因为0 - 1产生无符号类型的最大值,这个值大于size(仍然是0),因此循环永远不会进入。 两数之差可能为负数 # 当你想计算两个数之间的差(或增量)时,通常会将其表示为:delta = x - y;虽然大多数情况下不需要符号,因此你倾向于写并看到:delta = abs(x - y);这一论点认为无符号在这里是危险的,因为如果y > x那么你会得到下溢。这个论点的问题在于它是无效的,因为代码本身是错误的,无论x和y的符号如何。对于x和y的值都有

赞助内容

NordVPN Next-gen Antivirus

本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。

请我喝杯咖啡