【问题标题】:Is using unsigned integer overflow good practice?使用无符号整数溢出是好习惯吗?
【发布时间】:2009-06-12 19:29:23
【问题描述】:

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

【问题讨论】:

    标签: c++ c


    【解决方案1】:

    无符号整数溢出(以环绕形式)通常在散列函数中被利用,并且从年点开始。

    【讨论】:

    • 由于计算起源于密码学,我怀疑是在 1940 年代的某个时候。
    • 虽然我实际上不明白为什么要坚持 signed 整数溢出。使用无符号整数也一样好,可以用来检测溢出。
    • @Martin:在 UNIX 上。但 MS 自 1900 年以来一直在 Excel 中使用 Windows 和 1904 年在 Mac 上。
    • @eduard 我们正在谈论未签名的溢出。没有人坚持使用签名溢出 - 恰恰相反!
    • @Neil:对不起,“签名”在阅读其他答案/cmets 后卡在我的脑海里。
    【解决方案2】:

    简单地说:

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

    我>

    【讨论】:

      【解决方案3】:

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

      当然,我们希望在职程序员能够熟练掌握一门语言的合理特性,而不同的公司/团体对合理熟练程度的期望也不同。但是对于大多数群体来说,期望下一个人知道他/她的头脑而不必考虑它似乎有点过分。

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

      简而言之,我投票不这样做!

      【讨论】:

        【解决方案4】:

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

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

        【讨论】:

        • 我问的是无符号溢出
        • 这是未签名的。使用无符号算术,从 100 中减去 200。您将得到一个很大的值(大小取决于值的范围)。 C 中的无符号算术被定义为模块化,或者你可以说是环绕。
        • @DavidThornley:我认为问题在于依赖这种行为是否是一种好习惯。我个人的想法是,当且仅当人们每次希望强制执行它时都使用严格的类型转换时,依赖这种行为是可以的。如果 ab 是例如UInt32,表达式 (UInt32)(a-b) 将产生包装行为,很明显这种行为是预期的。然而,(ab) 应该产生包装行为的期望远不那么明显,并且在 int 大于 32 位的机器上,它实际上可能不会产生这种行为。
        【解决方案5】:

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

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

        只要你检查'now'和'then'之前的值,你就可以得到'now'和'then'的所有值。

        编辑:我想这确实是一个下溢。

        【讨论】:

        • 我相信上面的代码在int 是 64 位的系统上会失败,因为减法的操作数将扩展到有符号的 64 位整数,所以如果 'now' 是 0 并且then 是 4294967295u (0xFFFFFFFF) 减法的结果不是 1,而是 -4294967295。在比较之前将减法结果转换为 UInt32 可以避免该问题,因为 (UInt32)(-4294967295) 将为 1。
        【解决方案6】:

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

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

        其他替代方案并不那么整洁。除非您将 m 更改为有符号,否则尝试执行 m >= 0 不起作用,但是您可能会截断 n 的值 - 或者更糟 - 在初始化时将其转换为负数。

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

        【讨论】:

        • 或“做 {DoSomething(n);} while (n-- != 0);”。以“if (n != (unsigned)-1)”开头,如果实际上希望 -1 是一个意思是“什么都不做”的魔术输入值,就像上面的代码一样。
        • @Niki:不起作用,你的循环什么都不做,因为 m
        • 是的,通常是(无符号)-1 - 即最大可能的无符号是一个特殊值,但不一定是。
        • 我想我想在代码中明确说明它是否是一个经过特殊处理的有效输入,以确保那些阅读代码的人注意到。但是评论会和我的“如果”一样好,而不会产生额外的比较。
        • 为了代码的可读性,使用 UINT_MAX 不是比 (unsigned)-1 更好吗?此外,我认为这可能是执行 do...while 循环的正确位置; m=n;做{做某事(米); } 而(米-- > 0);将执行 0 案例
        【解决方案7】:

        我不会仅仅出于可读性的原因而依赖它。在确定将变量重置为 0 的位置之前,您将调试代码数小时。

        【讨论】:

        • 并非如此,只要记录和评论该行为即可。此外,(无)符号整数溢出也有合理的原因,例如在固定宽度的时间戳中,精度更重要,溢出可以被合理地检测到。
        【解决方案8】:

        只要你知道溢出会在什么时候发生就可以依赖溢出...

        例如,我在迁移到更新的编译器时遇到了 MD5 的 C 实现问题... 该代码确实预期溢出,但它也预期 32 位整数。

        64 位的结果是错误的!

        幸运的是,这就是自动化测试的用途:我很早就发现了问题,但如果没有引起注意,这可能是一个真正的恐怖故事。

        你可以争辩说“但这很少发生”:是的,但这就是让它变得更加危险的原因! 当出现错误时,每个人都会怀疑最近几天编写的代码。 没有人怀疑“工作多年”的 f 代码,通常没有人知道它是如何工作的......

        【讨论】:

        • 在这种情况下,编译时断言来检查 sizeof(int)==4 是否有效的假设将是一件好事。即使在测试运行之前,这也会引发错误。
        【解决方案9】:

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

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

        【讨论】:

        • 您指的是有符号整数。 C 标准记录了无符号整数的行为,这就是问题所在。
        【解决方案10】:

        如果您明智地使用它(注释和可读性都很好),您可以通过编写更小更快的代码从中受益。

        【讨论】:

          【解决方案11】:

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

          【讨论】:

            【解决方案12】:

            我建议在任何时候依赖无符号数字包装时始终进行显式转换。否则可能会有惊喜。例如,如果 "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 位的唯一可移植方法是在进行乘法之前将其中一个整数转换为 UInt64。否则 UInt32 可能会转换为有符号的 Int64,乘法会溢出并产生未定义的结果。

            【讨论】:

              猜你喜欢
              • 2012-01-03
              • 1970-01-01
              • 2012-02-29
              • 2014-07-24
              • 1970-01-01
              • 1970-01-01
              • 2023-03-22
              • 1970-01-01
              • 2015-02-18
              相关资源
              最近更新 更多