一些问题的答案在这里提到符号和无符号值之间的令人惊讶的促销规则,但这似乎更像是有关问题混合符号和无符号值,并且不一定解释为什么签署的变量会优先于无符号混合的场景之外。
以我的经验,除了混合比较和升级规则外,导致未签名的值成为吸引用户的原因有两个主要原因,如下所示。
无符号值的不连续性为零,这是编程中最常见的值
无符号整数和有符号整数在其最小值和最大值处均具有不连续性,它们在其中环绕(无符号)或导致未定义的行为(有符号)。因为unsigned
这些点在零和UINT_MAX
。因为int
他们在INT_MIN
和INT_MAX
。的典型值INT_MIN
和INT_MAX
与4字节的系统int
的值是-2^31
和2^31-1
,并且这样的系统上UINT_MAX
通常是2^32-1
。
导致错误的主要问题unsigned
不适用于int
它的不连续性为零。零当然是程序中非常常见的值,例如1,2,3等其他小值。通常在各种结构中添加和减去较小的值,尤其是1,并且如果从一个unsigned
值中减去任何值并且恰好为零,那么您将得到一个巨大的正值和一个几乎确定的错误。
考虑代码对索引中除最后0.5以外的所有值进行迭代:
for (size_t i = 0; i < v.size() - 1; i++) {
直到有一天您传递空向量之前,这都可以正常工作。而不是执行零次迭代,您将获得v.size() - 1 == a giant number
1并进行40亿次迭代,并且几乎有一个缓冲区溢出漏洞。
您需要这样写:
for (size_t i = 0; i + 1 < v.size(); i++) {
因此,在这种情况下可以对其进行“修复”,但必须仔细考虑的无符号性质size_t
。有时您无法应用上面的修正,因为您没有要应用的固定偏移量,而是要应用一些可变偏移量,该偏移量可以是正数或负数:因此,您需要进行比较的哪一面取决于有符号性-现在代码变得非常混乱。
尝试迭代到零(包括零)的代码也存在类似的问题。类似的东西while (index-- > 0)
可以正常工作,但是看起来等效的对象while (--index >= 0)
永远不会因无符号值而终止。当右侧的文字为零时,编译器可能会警告您,但如果它是在运行时确定的值,则肯定不会发出警告。
对点
有人可能会认为带符号的值也有两个不连续之处,那么为什么选择不带符号的呢?不同之处在于两个不连续点都非常(最大)远离零。我真的认为这是一个单独的“溢出”问题,有符号和无符号值都可能在很大的值上溢出。在许多情况下,由于值的可能范围的限制,不可能发生溢出,并且实际上在物理上不可能发生许多64位值的溢出。即使可能,与“零”错误相比,与溢出相关的错误的可能性也通常很小,并且对于无符号值也会发生溢出。因此,无符号结合了两种情况中最糟糕的情况:潜在的溢出具有非常大的幅度值,并且不连续为零。签名只有前者。
许多人会因未签名而争辩“您输了一点”。这通常是正确的-但并非总是如此(如果您需要表示无符号值之间的差异,则无论如何都会丢失该位:无论如何,如此多的32位内容仅限于2 GiB,或者您会在其中说一个奇怪的灰色区域一个文件可以是4 GiB,但不能在后2个GiB的一半上使用某些API)。
即使在未签名的情况下会给您带来一点好处:它也不会给您带来多少好处:如果您必须支持超过20亿个“事物”,那么您可能很快就会不得不支持超过40亿个。
逻辑上,无符号值是有符号值的子集
从数学上讲,无符号值(非负整数)是有符号整数(仅称为_integers)的子集。2。然而,有符号的值自然会从仅对无符号值(例如减法)的运算中弹出。我们可能会说未签名的值不会在减法后关闭。带符号的值并非如此。
是否想在文件的两个无符号索引之间找到“增量”?好吧,您最好按正确的顺序进行减法运算,否则您将得到错误的答案。当然,您通常需要运行时检查以确定正确的顺序!当将无符号值作为数字处理时,您经常会发现(逻辑上)带符号的值始终会出现,因此最好也从带符号开始。
对点
如上文脚注(2)所述,C ++中的带符号值实际上不是大小相同的无符号值的子集,因此,无符号值可以表示与带符号值可以表示的结果数量相同的结果。
是的,但是范围用处不大。请考虑减法和0到2N范围内的无符号数,以及-N到N范围内的有符号数。在_both和bth情况下,任意减法都会导致-2N到2N范围内的结果,并且任何一种整数只能表示一半。事实证明,以-N到N的零为中心的区域通常比0到2N范围更有用(在现实世界代码中包含更多实际结果)。考虑均匀分布以外的任何典型分布(对数,zipfian,正态分布等),并考虑从该分布中减去随机选择的值:以[-N,N]结尾的值多于[0,2N](实际上是结果分布)始终以零为中心)。
由于使用带符号的值作为数字的许多原因,64位关闭了大门
我认为上面的论点对于32位值已经很有说服力,但是对于32位值,确实发生了影响不同阈值的有符号和无符号的溢出情况,因为“ 20亿”这个数字可以被许多人超过抽象和物理量(数十亿美元,数十亿纳秒,包含数十亿个元素的数组)。因此,如果有人对无符号值的正范围加倍有足够的信心,那么他们可以说溢出确实很重要,并且它偏向于无符号。
在专用域之外,64位值在很大程度上消除了这种担忧。有符号的64位值的上限范围为9,223,372,036,854,775,807-超过九位数。那是很多纳秒(约292年的价值),而且很多钱。它也是一个更大的阵列,比任何计算机都可能在很长一段时间内在一致的地址空间中拥有RAM更大。那么,对于每个人来说(现在)9位数就足够了吗?
何时使用无符号值
请注意,样式指南并不禁止甚至不鼓励使用无符号数字。结论为:
不要仅使用无符号类型来断言变量是非负数。
实际上,无符号变量有很好的用途:
当您不希望将N位数量视为整数时,而只是将其视为“位包”。例如,作为位掩码或位图,或N个布尔值或其他值。这种用法通常与固定宽度类型(例如uint32_t
和)结合使用,uint64_t
因为您经常想知道变量的确切大小。一个特定的变量配得上这个治疗的暗示是,你只能在它与操作按位运算符,如~
,|
,&
,^
,>>
等等,而不是与算术操作,如+
,-
,*
,/
等。
在这里,无符号是理想的,因为按位运算符的行为是定义明确和标准化的。带符号的值有几个问题,例如移位时的不确定行为和不确定的表示形式以及不确定的表示形式。
当您实际需要模块化算术时。有时您实际上需要2 ^ N模块化算术。在这些情况下,“溢出”是功能而不是错误。由于将无符号值定义为使用模块化算术,因此它们可为您提供所需的信息。不能完全(轻松,有效地)使用带符号的值,因为它们具有未指定的表示形式并且未定义溢出。
0.5写完这篇文章后,我意识到这与Jarod的示例几乎完全相同,但我从未见过-出于充分的理由,这是一个很好的示例!
1我们在size_t
这里谈论的通常是32位系统上的2 ^ 32-1或64位系统上的2 ^ 64-1。
2 在C ++中,情况并非完全如此,因为无符号值在上端包含的值比对应的有符号类型更多,但是存在一个基本问题,即操作无符号值会导致(逻辑上)有符号值,但是没有相应的问题具有带符号的值(因为带符号的值已经包含无符号的值)。
unsigned int x = 0; --x;
,看看会x
变成什么样。如果没有限制检查,大小可能突然获得一些意外值,很容易导致UB。