是使用无符号整数溢出的好习惯吗?

我前几天正在阅读C标准,并注意到与有符号整数溢出(未定义)不同,无符号整数溢出是明确定义的。 我已经看到它在很多代码中用于最大化等等但是考虑到有关溢出的voodo,这被认为是很好的编程习惯吗? 它无论如何都不安全吗? 我知道像Python这样的许多现代语言都不支持它 – 相反,它们继续扩展大数字的大小。

无符号整数溢出(以环绕的forms)通常在散列函数中得到利用,并且从年开始就是点。

我可以想到无符号整数溢出导致问题的一种方法是从一个小的无符号值中减去,从而导致它包装到一个大的正值。

整数溢出的实用建议:
http://www.gnu.org/software/hello/manual/autoconf/Integer-Overflow-Basics.html#Integer-Overflow-Basics

简而言之:

只要您注意并遵守定义(无论出于何种目的 – 优化,超级聪明的算法等),您认为合适时使用无符号整数溢出是完全合法/可靠/安全的。

仅仅因为你知道标准的细节并不意味着维护你的代码的人。 在稍后调试时,该人可能不得不浪费时间担心这一点,或者必须查看标准以便稍后validation此行为。

当然,我们希望熟练掌握工作程序员的语言的合理特征 – 不同的公司/团队对合理的熟练程度有不同的期望。 但是对于大多数团体来说,这似乎有点太多了,期待下一个人知道他/她的头顶而不必考虑它。

如果这还不够,当你在标准的边缘工作时,你更有可能遇到编译器错误。 或者更糟糕的是,将此代码移植到新平台的人可能会遇到这些问题。

总之,我投票不做!

我一直用它来判断是否该做某事。

 UInt32 now = GetCurrentTime() if( now - then > 100 ) { // do something } 

只要你检查’now’laps’然后’之前的值,就可以了解’now’和’then’的所有值。

编辑:我想这真的是一个下溢。

我不会仅仅因为可读性原因而依赖它。 在确定将该变量重置为0之前,您将在几个小时内调试代码。

另一个可以有用地使用无符号溢出的地方是当你必须从给定的无符号类型向后迭代时:

 void DownFrom( unsigned n ) { unsigned m; for( m = n; m != (unsigned)-1; --m ) { DoSomething( m ); } } 

其他选择并不是那么简洁。 除非您将m更改为signed,否则尝试执行m >= 0不起作用,但是您可能会截断n的值 – 或者更糟 – 在初始化时将其转换为负数。

否则你必须做!= 0或> 0然后在循环后手动执行0情况。

由于CPU上的有符号数可以用不同的方式表示,因此99.999%的当前CPU使用二进制补码表示法。 由于这是大多数机器,因此很难找到不同的行为,尽管编译器可能会检查它(机会很大)。 但是,C规范必须占编译器的100%,因此尚未定义其行为。

所以它会让事情变得更加混乱,这是避免它的一个很好的理由。 但是,如果你有一个非常好的理由(例如,代码关键部分的性能提升因子为3),那么请将其记录下来并使用它。

只要你知道什么时候发生就可以依靠溢出……

例如,当迁移到更新的编译器时,我遇到了MD5的C实现的麻烦……代码确实期望溢出,但它也期望32位整数。

使用64位结果是错误的!

幸运的是,这就是自动化测试的目的:我尽早发现了问题,但如果不加注意,这可能是一个真正的恐怖故事。

你可以争辩说“但这种情况很少发生”:是的,但这就是让它变得更加危险的原因! 当有错误时,每个人都对过去几天写的代码持怀疑态度。 没有人怀疑“只是工作多年”的代码,通常没有人知道它是如何工作的……

如果您明智地使用它(很好地评论和阅读),您可以通过使用更小更快的代码来从中受益。

siukurnin提出了一个很好的观点,你需要知道什么时候会发生溢出。 避免他描述的可移植性问题的最简单方法是使用stdint.h中的固定宽度整数类型。 uint32_t是所有平台和操作系统上的无符号32位整数,在为不同的系统编译时不会有不同的行为。

我会建议在任何时候都要依赖无符号数字包装时进行显式转换。 否则可能会有惊喜。 例如,如果“int”是64位,则代码如下:

 UInt32 foo,bar; if ((foo-bar) < 100) // Report if foo is between bar and bar+99, inclusive) ... do something 

可能会失败,因为“foo”和“bar”会被提升为64位有符号整数。 在检查结果100之前将类型转换回添加回UInt32可以防止出现问题。

顺便说一句,我认为直接获取两个UInt32产品的底部32位的唯一可移植方法是在进行乘法之前将其中一个int强制转换为UInt64。 否则,UInt32可能会被转换为带符号的Int64,并且乘法溢出并产生未定义的结果。