为什么为size_t无符号?为什么为size_t无符号?(Why is size_t unsigne

2019-05-13 13:33发布

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

无符号的整数类型是理想的可治疗存储作为比特阵列的用途。 使用的,而不是一个int无符号,以获得一个更位代表正整数是几乎从来没有一个好主意。 试图确保某些值是通过声明的变量无符号通常被隐式转换规则被打败阳性。

为size_t似乎是无符号“以获得多一个比特来表示正整数”。 因此,这是一个错误(或折衷),如果是这样,我们应该在我们自己的代码中尽量少用呢?

斯科特迈尔斯另一个相关的文章是在这里 。 总之,他建议不要在接口中使用无符号,不管值是否始终是正。 换句话说, 即使负值是没有意义的,你不一定用签名。

Answer 1:

size_t是无符号的历史原因。

在16位的指针,如“小”模型DOS编程的结构中,这将是不现实的字符串限制为32字节。

出于这个原因,C标准要求(通过所需范围) ptrdiff_t ,经签署的副本来size_t和指针差的结果类型,得到有效17位。

这些理由仍然可以申请在嵌入式编程世界的组成部分。

然而,他们并不适用于现代的32位或64位编程,其中一个更重要的考虑是,C和C的不幸隐式转换规则++使无符号的类型分为错误吸引,当他们使用的数字(和因此,算术运算和幅度comparisions)。 随着事后诸葛亮,我们现在可以看到,决定采用这些特定的转换规则,其中如string( "Hi" ).length() < -3实际上是有保证的,是相当愚蠢的和不切实际的。 然而,这一决定意味着,在现代编程,采用无符号类型的数字有严重的缺点,并没有优势-除了满足那些谁找到的感情unsigned是一个自我描述型名称,不能想typedef int MyType

总而言之,这是不是一个错误。 这是因为当时很理性,实际的编程原因的决定。 它没有任何关系与边界检查语言传递的期望像帕斯卡尔C ++(这是一个谬论,而是一个非常非常常见的,即使一些人谁做的从来没有听说过的Pascal)。



Answer 2:

size_tunsigned ,因为负的大小并没有什么意义。

(从评论:)

它与其说是保证,作为说明什么。 当你是看到大小-1的名单是什么时候? 按照这种逻辑太远,你会发现签名应该根本不存在和位操作也不应被允许的。 - geekosaur

更重要的一点:地址,你应该想想原因,都没有签署。 大小通过比较产生的地址; 治疗对象的签署会做很多错误的事情的地址,并使用符号值的结果将在您的Stroustrup的报价显然阅读认为的方式丢失数据是可以接受的,但实际上并非如此。 也许你可以解释负地址应该做些什么来代替。 - geekosaur



Answer 3:

一种用于制备索引类型无符号的原因是为了用C和C ++的偏爱半开区间对称性。 如果你的索引类型将是无符号,那么它的方便,也有你的尺寸类型无符号。


在C语言中,你可以指向到一个数组的指针。 一个有效的指针可以指向过去阵列的端部的一个元件的阵列的任何元件或。 数组开始前,不能指向一个元素。

int a[2] = { 0, 1 };
int * p = a;  // OK
++p;  // OK, points to the second element
++p;  // Still OK, but you cannot dereference this one.
++p;  // Nope, now you've gone too far.
p = a;
--p;  // oops!  not allowed

C ++同意并扩展了这个想法迭代器。

针对无符号索引类型参数经常小跑了遍历从后到前的阵列的一个例子,以及将码经常看起来像这样:

// WARNING:  Possibly dangerous code.
int a[size] = ...;
for (index_type i = size - 1; i >= 0; --i) { ... }

此代码的工作index_type已签名,其被用作一个参数,索引类型应该被签名(并且通过扩展,尺寸应该被签名)。

这说法是没有说服力的,因为该代码是不地道。 看,如果我们试图改写这个循环使用指针,而不是指数会发生什么:

// WARNING:  Bad code.
int a[size] = ...;
for (int * p = a + size - 1; p >= a; --p) { ... }

哎呀,现在我们有不确定的操作! 忽略了问题,当size为0时,我们有在迭代结束时的问题,因为我们产生了无效的指针指向前的第一元素。 这就是即使我们从来没有尝试解引用该指针未定义行为。

所以,你可能会说通过改变语言标准,使之合法拥有指向前的第一个元素的指针来解决这个问题,但是这不太可能发生。 半开区间是这些语言的基本构建块,让我们写出更好的代码来代替。

一个正确的基于指针的解决方案是:

int a[size] = ...;
for (int * p = a + size; p != a; ) {
  --p;
  ...
}

许多人认为这种令人不安的,因为减量现在在循环的身体,而不是在头,但那个时候你的语法主要是为着会发生什么遍历半开区间。 (反向迭代通过推迟减量解决这种不对称。)

现在,通过类比,指数化解决方案变为:

int a[size] = ...;
for (index_type i = size; i != 0; ) {
  --i;
  ...
}

这工作是否index_type带符号,而是更直接地映射到地道的指针和迭代器版本还未签约的收益代码。 无符号也意味着,与指针和迭代,我们将能够访问该序列的每一个元素 - 我们不投降一半的可能范围,以代表荒谬的价值观。 虽然这不是在一个64位的世界一个实用的关注,它可以在一个16位嵌入式处理器或在超过一个巨大的范围构建为稀疏的数据的抽象的容器类型一个非常现实的担心,仍然可以提供相同的API作为本机容器。



Answer 4:

另一方面 ...

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

有通常所说的这里有两个“历史”的原因:

  1. sizeof返回std::size_t ,因为C的日子已经无符号
  2. 处理器有更小的字大小,所以挤的范围,额外位出来是很重要的。

但无论这些原因,尽管是很老了,实际上成为历史。

sizeof还是返回std::size_t这仍然是无符号。 如果你想与互操作sizeof或标准库的容器,你将不得不使用std::size_t

该方案是更糟糕:您可以禁用符号/无符号比较警告和大小变换警告和希望值始终处于重叠范围,这样就可以使用不同类型的夫妇有可能引进忽略了潜在的错误。 或者你可以做很多范围检查和显式转换。 或者你可以用巧妙的内置转换介绍自己的尺寸类型集中的范围检查,但没有其他的库将要使用你的尺寸类型。

虽然大部分的主流计算是在32位和64位处理器完成,C ++仍然在嵌入式系统中,即使在今天使用的16位微处理器。 在这些微处理器,它往往是非常有用的,有一个字大小的值,它可以表示你的存储空间的任何值。

我们新的代码仍然与标准库进行互操作。 如果使用符号类型我们新的代码,而标准库继续使用无符号的,我们使它更难有同时使用每一个消费者。

误区2:你不需要额外位。 (AKA,你永远不会有一个字符串大于2GB当你的地址空间只有4GB。)

大小和索引不只是记忆。 您的地址空间可能是有限的,但你可以处理那些比你的地址空间更大的文件。 虽然你可能不会有更多的2GB的字符串,你可以轻松拥有一个bitset比2Gbits更多。 而且不要忘了专为稀疏数据的虚拟容器。

误区3:您可以随时使用更宽的符号类型。

不总是。 的确,对于一个局部变量或两个,你可以使用std::int64_t (假设你的系统有一个)或signed long long大概写完全合理的代码。 (但你还是会需要一些显式转换和两倍的边界检查或者你就必须禁用可能会已通知您在代码中的其他地方的bug一些编译器警告。)

但是,如果你正在构建的指数的一个大表? 难道你真的想为每一个指标一个额外的两个或四个字节 ,当你只需要一个 ? 即使你有足够的内存和一个现代化的处理器,使得该表的两倍会对引用的局部性有害影响,那么你所有范围检查现在两个步骤,减少了分支预测的有效性。 如果你没有的东西所有的记忆?

误区4:无符号运算是令人惊讶和不自然。

这意味着, 符号的算术是不足为奇的,或在某种程度上更自然。 而且,也许是在数学,所有的基本算术运算都关闭了所有整数的角度思考时。

但是我们的电脑不整数的工作。 他们与整数无穷小部分工作。 我们签署了算术不关闭在所有整数的集合。 我们有溢和下溢。 对许多人来说,这是那么令人惊讶和不自然,他们大多只是忽略它。

这是错误:

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

如果minmax的签署,总和可能溢出,并产生不确定的行为。 我们大多数人经常错过这个这类错误的,因为我们忘记了另外未超过设定签署整数关闭。 我们过关了,因为我们的编译器通常生成的代码,做了合理的(但仍然令人惊讶)。

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

真正的无符号的惊喜配备了减法:如果您从一个较小的一个减去一个较大的无符号整数,你要与一个巨大的数字就结了。 这个结果并不比如果除以0更令人惊讶。

即使你能消除无符号类型,从所有的API,你仍然对这些未签名“惊喜”做好准备,如果你处理的标准集装箱或文件格式或线协议。 是不是真的值得加入摩擦你的API来“解决”只是问题的一部分?



文章来源: Why is size_t unsigned?