大小应该使用有符号整数还是无符号整数

Should signed or unsigned integers be used for sizes?

本文关键字:整数 符号 无符号整数      更新时间:2023-10-16

标准库vector::size()给出一个size_t,一个无符号数。在CppCon的一次演讲中,我听到有人(是Chandler Carruth吗?)说这很不幸,它应该使用有符号整数。

背景是溢出不是为有符号整数定义的,因此编译器有更多的余地。在一次演讲中,Carruth展示了作为bzip2中for循环索引的uint8_t如何在x86上创建比int8_t多得多的机器指令,因为它必须用掩码和移位显式地模拟溢出。

在我现在处理的代码中,有一些大小是严格正的。这些被表示为CCD_ 6。这看起来不错,因为这表明他们不可能是消极的。另一方面,不需要定义的模运算,所以只要有符号整数足够大(我们转到200),无符号整数就会为我们想要的运算提供错误的接口。

在代码中的某个点上,存在从0到该大小的循环。然后减去循环索引,取绝对值。

当我用更现代的GCC 7编译它时,它无法解决std::abs的适当过载问题,因为size_t - size_t显然给出了令人震惊的值。我已将代码更改为在循环索引中使用int

for (int t1 = 0; t1 < Lt; t1++) {
for (int t2 = 0; t2 < Lt; t2++) {

现在abs(t1 - t2)运行良好。但是比较t1 < Lt给出了一个警告,因为它是有符号和无符号数字之间的比较。

什么是正确的方法?

  1. 对任何非负的值使用无符号整数,然后在需要执行子运算时使用static_cast<int>()
  2. 循环索引使用有符号整数,容器大小使用无符号整数。然后在比较中使用static_cast<int>
  3. 只需在任何地方使用有符号整数。当其他库返回无符号整数时,只需在那里使用static_cast<int>即可满足警告

"在一次演讲中,Carruth展示了uint8_t作为bzip2中的for循环索引如何在x86上创建比int8_t多得多的机器指令,因为它必须用掩码和移位显式地模拟溢出。">

如果可以使用任意一种类型,则for范围必须限制为[0, 127]。那么,只需使用int作为索引类型。根据定义,它是基本数学运算的自然类型,通常很好地映射到CPU寄存器。

使用为最小存储量优化的类型不会产生最快的数学结果。这并不奇怪。基于这种有缺陷的设置,你无法得出关于已签名与未签名的结论。

"size_t-size_t给出不明确的值">

它没有,但它确实使用了模运算。size_t(1)-size_t(2)==size_t(-1),但size_t(-1)是最大可能的值。这直接源于模块数学的定义:x-1 < x,除非x-1由于x==0而环绕。(或等效x+1>xx+1==0除外)

因此调用abs(size_t(x))也是毫无意义的,因为每个size_t值都是正的。将有符号整数与size_t进行比较同样充满了意想不到的后果。显式类型转换很好,因为它们可以清楚地显示结果。

但是,并没有一个通用的解决方案来自动确定应该应用哪种类型。如果可以发明一个机械规则,我们就可以把这个规则留给编译器。我们没有,因为我们做不到。作为一名程序员,你必须用数字来考虑每一种情况。