为什么size_t没有签名?

Bjarne Stroustrup在C ++编程语言中写道:

无符号整数types非常适合将存储器视为位数组的用途。 使用unsigned而不是int来获得更多的位来表示正整数几乎不是一个好主意。 尝试通过声明variables无符号来确保某些值是正值将通常被隐式转换规则所破坏。

size_t似乎是无符号的“以获得更多的位来表示正整数”。 那么这是一个错误(或权衡),如果是这样,我们应该尽量减less在我们自己的代码中使用它?

Scott Meyers的另一篇相关文章就在这里 。 总之,他build议不要使用无符号的接口,不pipe值是否总是正值。 换句话说, 即使负值是没有意义的,你也不一定要使用无符号的。

由于历史原因, size_t是无符号的。

在具有16位指针的体系结构(如“小型”DOS编程)中,将string限制为32 KB是不切实际的。

为此,C标准要求(通过所需范围) ptrdiff_tsize_t的有符号对应部分和指针差异的结果types)有效地为17位。

这些原因仍然适用于embedded式编程领域的某些部分。

然而,它们不适用于现代的32位或64位编程,其中一个更重要的考虑是C和C ++的不幸的隐式转换规则将无符号types转化为臭虫吸引子,当它们用于数字(和因此,算术操作和量级比较)。 事后我们现在可以看到,采用这些特定的转换规则,例如string( "Hi" ).length() < -3的决定实际上是有保证的,是相当愚蠢和不切实际的。 但是,这个决定意味着在现代编程中,采用无符号types的数字具有严重的缺点和缺点 – 除了满足那些unsigned人的感觉是一个自描述types名称,并且不能想到typedef int MyType

总结一下,这不是一个错误。 这是一个非常合理的,实际的编程原因的决定。 这与把从Pascal这样的边界检查语言转向C ++(这是一个谬误,但是非常普遍的一个,即使其中一些人从未听说过Pascal)没有任何关系。

size_tunsigned因为负尺寸是没有意义的。

(从评论:)

没有太多的确保,说明什么是。 你最后一次看到大小为-1的列表是什么时候? 按照这个逻辑太远,你发现无符号不应该存在,位操作也不应该被允许。 – geekosaur

更重要的是:地址,因为你应该考虑的原因,没有签署。 尺寸是通过比较地址生成的; 将地址作为签名来处理将会做错误的事情,而对结果使用有符号的值会丢失数据,因此您阅读Stroustrup的引用显然认为是可以接受的,但实际上并不是这样。 也许你可以解释一个负面的地址应该做什么。 – geekosaur

另一方面 …

神话1std::size_t是无符号的,因为传统的限制不再适用。

这里通常提到两个“历史”的原因:

  1. sizeof返回std::size_t ,自C的日子以来一直没有签名。
  2. 处理器字数较小,所以重要的是要挤出额外的范围。

但是,这些原因,尽pipe很古老,但实际上已经降级到历史。

sizeof仍然返回一个仍然没有签名的std::size_t 。 如果你想与sizeof或标准库容器交互操作,你将不得不使用std::size_t

替代scheme更糟糕:您可以禁用已签名/未签名的比较警告和大小转换警告,并希望这些值始终处于重叠范围,以便您可以忽略使用不同types的潜在错误(可能会引入)。 或者你可以做很多范围检查和明确的转换。 或者你可以引入你自己的大小types与聪明的内置转换集中范围检查,但没有其他图书馆将使用您的大小types。

尽pipe大多数主stream计算是在32位和64位处理器上完成的,但即使在今天,C ++仍然被用在embedded式系统中的16位微处理器上。 在这些微处理器上,有一个可以表示内存空间中任何值的字大小的值通常是非常有用的。

我们的新代码仍然需要与标准库互操作。 如果我们的新代码使用签名types,而标准库继续使用未签名types,那么我们使每个使用两者的用户更难。

神话2 :你不需要额外的一点。 (也就是说,当你的地址空间只有4GB的时候,你永远不会有一个大于2GB的string)

大小和索引不只是为了记忆。 您的地址空间可能有限,但您可能会处理比您的地址空间大得多的文件。 虽然你可能没有更多的2GB的string,你可以舒适地有超过2Gbits bitset。 不要忘记为稀疏数据而devise的虚拟容器。

神话3 :你总是可以使用更宽的签名types。

不总是。 确实,对于一个或两个局部variables,你可以使用std::int64_t (假设你的系统有一个)或signed long long并可能写出完全合理的代码。 (但是你仍然需要一些明确的强制转换和两倍的边界检查,否则你将不得不禁用一些编译器警告,这些警告可能会提醒你在代码中的其他地方出现bug。)

但是如果你正在build立一个大型的指数表呢? 当你只需要一个时,你是否真的需要额外的两个或四个字节的索引? 即使你有足够的内存和一个现代化的处理器,把表格翻两番,可能会对参考的位置产生有害的影响,而且所有的范围检查现在是两步,降低了分支预测的有效性。 而如果你没有所有的记忆呢?

神话4 :无符号算术是令人惊讶和不自然的。

这意味着有符号的算术并不奇怪,或者更自然。 而且,也许是从数​​学的angular度来看,所有基本的算术运算都是在所有整数的集合上closures的。

但是我们的电脑不能使用整数。 他们使用整数的无穷小部分。 我们的签名算术没有closures所有整数的集合。 我们有溢出和下溢。 对很多人来说,这是如此令人惊讶和不自然,他们大多只是忽略它。

这是错误:

 auto mid = (min + max) / 2; // BUGGY 

如果minmax被签名,那么总和可能会溢出,并产生未定义的行为。 我们大多数人经常错过这种types的错误,因为我们忘记了添加不是关于一组签名的整数。 我们逃避了,因为我们的编译器通常会生成一些合理的代码(但仍然令人惊讶)。

如果minmax是无符号的,则总和仍然可以溢出,但是未定义的行为已经消失。 你仍然会得到错误的答案,所以它仍然令人惊讶,但不会比有符号整数更令人惊讶。

真正的未经签名的意外来自减法:如果从较小的一个中减去一个较大的无符号整数,那么最终会得到一个大数字。 这个结果没有比用0除以更令人惊讶的了。

即使您可以从所有API中删除未签名的types,但是如果处理标准容器或文件格式或有线协议,仍然必须为这些未签名的“惊喜”做好准备。 是否真的值得添加摩擦到您的API“解决”只是部分的问题?