AI生成内容几乎总是匿名发布
速览
一项最新调查显示,由AI生成的文本、图像等内容在网络上发布时,几乎从未附带来源或作者签名。这引发了关于版权归属和内容可信度的讨论。研究者呼吁平台和用户增加透明度。
AI 深度解读
背景
在编程实践中,整数类型的选择长期存在争议。大多数整数在程序中永远不会表示负值(例如数组索引、循环计数),按理说无符号整数应该比有符号整数更常用。然而,在实际代码中,开发者几乎总是默认选择有符号整数。这篇文章由 Dale Weiler 撰写,发布于 Hacker News,主要针对 C 和 C++ 语言,同时也涉及 Go、Rust 和 Odin 等语言,试图论证这一原则适用于所有语言——无论语言对整型溢出的处理方式如何(例如 C/C++ 将有符号整数溢出定义为未定义行为),但问题本质上是算术运算本身的特性。
核心内容
反对无符号整数的常见论点及其反驳
安全性论点
最常见的反对理由是:无符号整数更容易发生下溢(underflow),比上溢更危险。这一建议如此普遍,以至于 Google 官方 C++ 编码规范明确反对使用无符号类型。然而,作者认为这些安全性问题源于不当的使用习惯,通过简单惯用法可以轻松避免,比处处使用有符号整数更容易理解。实际上,坚持使用有符号整数反而会助长不安全代码的编写。
反向循环论点
当 for 循环需要反向计数,且循环体在计数器为零时仍需执行时,多数程序员认为无符号整数难以使用,因为 i >= 0 永远为真。常见的做法是强制转换成有符号整数:
for (int64_t i = (int64_t)size - 1; i >= 0; i--) { ... }
这很危险:它涉及窄化转换,强制转换会压制合法警告。在 C/C++ 中,当 size 为特定大值时会触发未定义行为(例如 size >= 0x7fffffffffffffff 时,强制转换后的 i 可能变成负值,导致越界访问)。反对者常辩称这种输入是“病态的”,但作者指出这种说法不仅错误,而且更危险——因为 int64_t 根本不允许大于 0x7fffffffffffffff 的值,只是回避了问题。如果你真的需要那么大值,则必然触发有符号溢出(未定义行为),后果更严重。
正确的做法是利用无符号整数的回绕(wrap)特性(C/C++ 中无符号下溢是明确定义的):
for (size_t i = size - 1; i < size; i--) {
// ...
}
从 size-1 开始,每次递减。当计数器为零时,递减操作使计数器下溢回绕到无符号类型的最大值,该值远大于 size,因此条件 i < size 为假,循环终止。即使 size == 0,0 - 1 产生最大值 > 0,循环不会进入。
在 Rust 中,Debug 构建下无符号下溢会触发 panic,但可以用 Range 特性安全实现:
for i in (0..size).rev() {
// ...
}
无需强制转换,无隐蔽错误,所有合法输入均正常工作,覆盖 [0, 0xffffffffffffffff) 整个范围。
两个数的差可能为负数
当需要计算两个数的差(或绝对值差)时,常写成 delta = x - y; 或 delta = abs(x - y);。反对者认为无符号不安全,因为若 y > x 则下溢。但作者指出,这个论证无效,因为无论 x、y 有无符号,代码本身就有问题:对于有符号整数,同样存在可能的下溢(比如 x 为负或 y 为正时),在 C/C++ 中同样触发未定义行为。而且即使是支持回绕的语言,INT_MAX - INT_MIN 的结果也是错误的。安全计算差值对有符号整数而言极其困难,例如:
if ((y > 0 && x < INT_MIN + y) || (y < 0 && x > INT_MAX + y)) {
// error
} else {
delta = abs(x - y);
}
而对无符号整数,只需:
delta = max(x, y) - min(x, y);
总是安全给出绝对值差,表达式自文档化,可读性也更好。
用有符号计算索引更安全
反对者声称,对于更复杂的索引表达式(如区间中点),使用有符号更安全。作者以二分查找中常见的中值计算为例:
int mid = (low + high) / 2;
当 low + high 超过 2^31-1 时,有符号溢出为负值,结果错误。即使改用更大的有符号类型(如 int64_t)也无法避免,因为仍可能超过 2^63-1。事实上,在任何语言中,安全计算两个变量的中点都几乎不可能——即使考虑回绕,也存在特定输入导致失败。常见改进写法 int mid = low + (high - low) / 2; 在 high = INT_MAX, low = INT_MIN 时仍然失败。
若使用无符号整数,直接写 size_t mid = (low + high) / 2; 在 low = 0x80000000, high = 0x80000002 时也会下溢得到错误结果 1(正确值 0x80000001)。但是,当这些值用作数组索引时,有符号行为几乎肯定会产生无效索引,导致内存安全问题;而无符号版本虽然结果可能不是精确中点,但索引始终在数组范围内(不会越界)。
关键要点
- 无符号整数是默认选择:大多数整数从不表示负值(数组索引、循环计数),因此应优先使用无符号类型,而非有符号。
- 安全性的真正来源是用法而非类型:所谓无符号易下溢的论点忽略了有符号溢出更危险(未定义行为,可能导致安全漏洞)。正确惯用法(如反向循环的
i < size模式)简单且安全。 - 反向循环的优雅解法:利用无符号回绕特性,用递减后与 size 比较代替
i >= 0,可安全处理包括 size=0 在内的所有情况。Rust 等语言可通过.rev()方法直接实现。 - 绝对值差计算:对无符号整数,
max(x,y) - min(x,y)总是安全且自解释;对有符号整数则需复杂检查。 - 中点计算并非无符号的弱点:无论有无符号,安全计算中点都困难。但作为数组索引时,无符号结果不会越界,有符号则可能导致内存安全问题。
- 语言差异:C/C++ 无符号回绕是明确定义的;Rust Debug 模式会 panic,但用 Range 可安全实现相同行为;Go/Odin 有符号也回绕,但可能导致逻辑错误。
- 类型转换的危险:强制将无符号转为有符号(如
int64_t i = (int64_t)size - 1)引入窄化转换和未定义行为,应避免。
意义与影响
这篇文章挑战了业界长期存在的“默认使用有符号整数”的惯例(尤其是受 Google C++ 编码规范影响)。它指出,许多反对无符号的理由源于对下溢/上溢行为的误解,以及对未定义行为的忽视。作者通过具体代码示例证明,采用无符号整数配合简单惯用法,不仅更安全(避免未定义行为),还能覆盖更大的数值范围,同时保持代码清晰。这一观点对系统编程(C/C++)、并发语言(Go/Rust)等场景具有实际指导意义:开发者应重新审视类型选择,认识到有符号整数的溢出风险远大于无符号的下溢风险,尤其是在涉及内存安全时。文章也提醒语言设计者,明确定义的算术行为(如 Rust 的 Debug 检查 + Release 回绕)比模糊的未定义行为更有利于编写健壮代码。
