关于无符号整数的最佳实践是什么?


43

我到处都使用unsigned int,但不确定是否应该这样做。可以是从数据库主键ID列到计数器等。如果数字永远不能为负,那么我将始终使用无符号整数。

但是我从其他人的代码中注意到,似乎没有其他人可以这样做。我忽略了一些关键的事情吗?

编辑:由于这个问题,我也注意到在C语言中,返回错误的负值是司空见惯的,而不是像C ++中那样引发异常。


26
只是当心for(unsigned int n = 10; n >= 0; n --)(无限循环)
克里斯·伯特·布朗

3
在C和C ++中,无符号int具有精确定义的溢出行为(模2 ^ n)。带符号的整数不。优化器越来越多地利用这种不确定的溢出行为,在某些情况下会导致令人惊讶的结果。
2011年

2
好问题!我也曾经尝试过使用限制范围,但发现风险/不便的确超过了任何好处/不便。正如您所说,大多数库都接受uint可以使用的常规int。这使它很难使用,但也引出了一个问题:值得吗?在实践中(假设您不会以愚蠢的方式做事),在期望值是正数的情况下,您很少会得到-218的值。那-218一定来自某个地方吧?您可以追踪其起源。很少发生。一定要利用断言,异常,代码契约来为您提供帮助。
工作

@William Ting:如果仅涉及C / C ++,则应在问题中添加适当的标签。
CesarGon 2011年

2
@Chris:无限循环问题在现实中有多重要?我的意思是,如果确实将其发布,则显然未对代码进行测试。即使在第一次出现此错误时需要花费几个小时进行调试时,第二次您也应该知道在代码不停止循环时首先要查找的内容。
安全

Answers:


28

我忽略了一些关键的事情吗?

当计算涉及有符号和无符号类型以及不同大小时,类型提升的规则可能会很复杂,并导致意外行为

我相信这是Java省略unsigned int类型的主要原因。


3
另一种解决方案是要求您适当地手动转换数字。这就是Go似乎要做的事情(尽管我只玩了一点),而且比Java的方法更喜欢它。
Tikhon Jelvis'7

2
这是Java不包含64位无符号类型的一个很好的理由,也许是一个不包含32位无符号类型的不错的理由[尽管添加有符号和无符号32位值的语义并不难-这样的操作应仅产生64位带符号的结果]。小于的无符号类型int不会造成这种困难(但是,因为任何计算都会提升为int);关于缺少无符号字节类型,我没有什么好说的。
supercat 2014年

17

我认为Michael是有道理的,但是IMO之所以每个人都一直使用int的原因(尤其是在中for (int i = 0; i < max, i++)是因为我们是这样学习的。当“ 如何学习编程 ”书中的每个示例都循环使用intfor,很少有人会质疑这种做法。

另一个原因是int比短25%uint,我们都很懒... ;-)


2
我同意教育问题。大多数人似乎从来没有质疑过他们所读的东西:如果书中有书,那肯定不会错吧?
Matthieu M.

1
这也就是为什么每个人都++在增加时使用后缀的原因,尽管事实上很少需要它的特殊行为,如果循环索引是迭代器或其他非基本类型(或者编译器确实很密集),甚至可能导致毫无意义地复制副本。 。
underscore_d

只是不要做“ for(uint i = 10; i> = 0; --i)”之类的事情。仅将int用于循环变量可避免这种情况。
David Thornley


8

混合有符号和无符号类型会使您陷入痛苦。而且,您不能使用所有无符号类型,因为您会遇到有效范围包括负数或需要一个值来指示错误且-1是最自然的东西。因此,最终结果是许多程序员都使用所有有符号整数类型。


1
最好不要在同一个变量中混合使用带有错误指示的有效值,并为此使用单独的变量。当然,C标准库在这里没有树立好榜样。
确保

7

对我而言,沟通非常重要。通过显式使用unsigned int,您可以告诉我带符号的值不是有效值。这使我在读取代码时除了变量名外还可以添加一些信息。理想情况下,我可以使用非匿名类型告诉我更多信息,但是,与在各处都使用int相比,它可以为我提供更多信息。

不幸的是,并不是每个人都非常了解他们的代码所传达的内容,这也许就是即使值至少是无符号的,您仍然在各处看到整数的原因。


4
但是我可能只想将一个月的值限制为1到12。是否要使用其他类型?那一个月呢?某些语言实际上允许这样的限制值。其他如.Net / C#提供代码合同。当然,非负整数经常出现,但是大多数支持这种类型的语言都不支持进一步的限制。因此,应该混合使用uint和错误检查,还是通过错误检查来完成所有工作?大多数库都没有要求uint在哪里使用它才有意义,因此使用one和强制转换可能会很不方便。
工作

@Job我想说您应该在您的月份中使用某种编译器/解释器强制实施的限制。它可能会给您一些样板,但将来您会受到强制性限制,可以防止错误并更清晰地传达您的期望。实施过程中,防止错误和简化沟通比带来不便更为重要。
daramarak

1
“我可能只希望将一个月的值限制为1到12”。如果您有一组有限的值(例如月份),则应使用枚举类型,而不是原始整数。
乔什·卡斯威尔

6

unsigned int在C ++中主要用于数组索引,以及用于任何从0开始的计数器。我认为最好明确地说“此变量不能为负数”。


14
您可能应该在c ++中为此使用size_t
JohnB 2011年

2
我知道,我就是不被打扰。
quant_dev

3

在处理实际上可能接近或超过有符号int限制的整数时,您应该注意这一点。由于32位整数的正最大值为2,147,483,647,因此,如果您知道a)永不为负,并且b)可能达到2,147,483,648,则应使用无符号整数。在大多数情况下,包括数据库密钥和计数器,我什至都不会处理这些数字,因此我不必担心自己是否担心符号位用于数字值或指示符号。

我会说:使用int,除非您知道需要一个unsigned int。


2
当使用可以达到最大值的值时,无论符号如何,都应开始检查整数溢出的操作。对于无符号类型,这些检查通常比较容易,因为大多数操作的结果定义良好,而没有未定义和实现定义的行为。
确保

3

在简单性和可靠性之间进行权衡。编译时捕获的错误越多,软件的可靠性就越高。在这个范围内,不同的人和组织处于不同的观点。

如果您曾经在Ada中进行任何高可靠性编程,甚至会对变量(例如,以英尺为单位的距离与以米为单位的距离)使用不同类型的变量,并且如果您不小心将一个变量分配给另一个变量,则编译器会对其进行标记。这对于编程制导导弹来说是完美的选择,但是如果您要验证网络表单,则可能会过度杀伤(双关语是故意的)。只要符合要求,这两种方式都不一定有任何问题。


2

我倾向于同意乔尔·埃瑟顿的推理,但得出相反的结论。我的看法是,即使您知道数字不太可能接近带符号类型的限制,如果您知道不会出现负数,那么使用类型的带符号变体的理由就很少了。

出于同样的原因,为什么在少数选择的实例中,在SQL Server表中使用BIGINT(64位整数)而不是INTEGER(32位整数)。数据在任何合理的时间内达到32位限制的可能性很小,但是,如果发生这种情况,则在某些情况下的后果可能是毁灭性的。只需确保在各种语言之间正确映射类型,否则您将最终产生有趣的怪异...

就是说,对于某些事情,例如数据库主键值,带符号或无符号确实没有关系,因为除非您手动修复损坏的数据或类似的东西,否则您将永远不会直接处理该值。这是一个标识符,仅此而已。在这些情况下,一致性可能比正确选择签名更为重要。否则,您最终将获得一些带符号的外键列,而另一些则是无符号的外键列,没有明显的样式-或再次是有趣的怪异。


如果您使用的是从SAP系统提取的数据,则强烈建议将BIGINT用于ID字段(例如CustomerNumber,ArticleNumber等)。只要没有人使用字母数字字符串作为ID,那就是…… 感叹
Treb

1

我建议在空间受限的外部数据存储和数据交换上下文中,通常应使用带符号的类型。在大多数情况下,今天32位有符号整数太小而32位无符号值就足够了,不久之后32位无符号值也不会足够大。

人们应该使用无符号类型的主要时间是将多个值组合为一个较大的值(例如,将四个字节转换为一个32位数字),或者将较大的值分解为较小的值(例如,将32位数字存储为四个字节)。 ),或者需要定期处理的数量(例如住宅用电表;其中大多数都有足够的数字以确保在读数之间不会发生翻滚) (如果一年读取三遍,但不足以确保它们不会在电表的使用寿命内滚动)。无符号类型通常具有足够的“怪异性”,因此仅在需要其语义的情况下才应使用它们。


1
“我建议通常使用带符号的类型。” 嗯,您忘了提到带符号类型的优点,而只列出了何时使用无符号类型。“怪异”?尽管大多数未签名的操作都有明确定义的行为和结果,但是在使用签名类型(溢出,移位,...)时,您将输入未定义和实现定义的行为。您在这里对“怪异”有一个奇怪的定义。
确保

1
@Secure:我所指的“怪异”与比较运算符的语义有关,尤其是在涉及混合有符号和无符号类型的操作中。正确的是,当使用足够大的值来溢出时,有符号类型的行为是不确定的,但是即使处理相对较小的数字,无符号类型的行为也可能令人惊讶。例如,(-3)+(1u)大于-1。同样,一些适用于数字的常规数学关联关系不适用于无符号的。例如,(ab)> c并不意味着(ac)> b。
超级猫

1
@Secure:虽然确实不能总是依赖于带有“大”号的数字的关联行为,但是当处理相对于带符号整数的域“小”的数字时,这些行为确实可以正常工作。相比之下,上述非关联在无符号值“ 2 3 1”下是有问题的。顺便说一句,当使用超出本机字大小的值时,有符号的行为在不受限制地使用时具有不确定的行为这一事实可以允许在某些平台上改进代码生成。
超级猫

1
如果这些评论首先出现在您的回答中,而不是没有给出任何理由的建议和“呼唤”,我将不会对此发表评论。;)尽管我仍然不同意“怪异”,但这只是类型的定义。当然,为给定的工作使用正确的工具,并了解该工具。当您需要+/-关系时,无符号类型是错误的工具。没有size_t签名并ptrdiff_t签名的原因是有原因的。
确保

1
@Secure:如果要表示一个位序列,则无符号类型非常有用;我认为我们同意。并且在某些小型微米上,无符号类型对于数值量可能更有效。在增量代表数值但实际值不代表增量的情况下(例如TCP序列号),它们也很有用。另一方面,任何时候只要减去无符号值,就不得不担心极端情况,即使数字很小也是如此。带有符号值的此类数学仅在数字很大时才会出现极端情况。
超级猫

1

我使用无符号整数使我的代码及其意图更加清楚。在对有符号和无符号类型进行算术运算时,为防止意外的隐式转换,我要做的一件事是对我的无符号变量使用无符号短型(通常为2个字节)。这是有效的,原因有两个:

  • 当您对无符号的short变量和文字(int类型)或int类型的变量进行算术运算时,这可确保在评估表达式之前将unsigned变量始终提升为int,因为int始终具有比short更高的排名。 。这当然可以避免表达式表达式的结果适合带符号的int的情况,从而避免了对带符号和无符号的类型进行算术运算的任何意外行为。
  • 在大多数情况下,您使用的无符号变量不会超过2字节无符号短整数的最大值(65,535)

一般原则是,无符号变量的类型应比有符号变量的类型具有较低的等级,以确保提升为有符号类型。这样就不会有任何意外的溢出行为。显然,您不能一直确保这一点,但是(大多数)通常可以确保这一点。

例如,最近我有一些for循环,如下所示:

const unsigned short cuint = 5;
for(unsigned short i=0; i<10; ++i)
{
    if((i-2)%cuint == 0)
    {
       //Do something
    }
}

文字“ 2”的类型为int。如果i是无符号int而不是无符号short,则在子表达式(i-2)中,2将被提升为无符号int(因为unsigned int的优先级高于signed int)。如果i = 0,则子表达式等于(0u-2u)=由于溢出而产生的较大值。i = 1时的想法相同。但是,由于i是一个无符号的short,因此它被提升为与文字'2'相同的类型,后者的符号为int,并且一切正常。

为了增加安全性:在极少数情况下,您要在其上实现的体系结构将int设为2个字节,这可能会导致在无符号short变量不合适的情况下,将算术表达式中的两个操作数都提升为unsigned int到带符号的2字节int中,后者的最大值为32,767 <65,535。(有关更多详细信息,请参见https://stackoverflow.com/questions/17832815/c-implicit-conversion-signed-unsigned)。为了防止这种情况,您可以简单地在程序中添加static_assert,如下所示:

static_assert(sizeof(int) == 4, "int must be 4 bytes");

并且不会在int为2个字节的体系结构上进行编译。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.