【问题标题】:Why does modulo division go wrong for mix of size_t and unsigned int in C++为什么 C++ 中 size_t 和 unsigned int 的混合模除会出错
【发布时间】:2021-05-17 10:32:23
【问题描述】:

给定一个程序

#include <iostream>
using namespace std;

int main()
{
     const size_t DoW = 7;
     const unsigned int DAYS_OF_WEEK = static_cast<unsigned int> (DoW);
     unsigned int dayOfFirstDay = 0;
     unsigned int _firstDayOfWeek = 1;
     unsigned int diff = (DAYS_OF_WEEK+ (dayOfFirstDay - _firstDayOfWeek) ) % DAYS_OF_WEEK;
     cout << "diff = ("  << DAYS_OF_WEEK << " + (" << dayOfFirstDay << " - " << _firstDayOfWeek << ")) %" << DAYS_OF_WEEK
         << " = " << diff << endl;
     return 0;
}

该程序的输出是

diff = (7 + (0 - 1)) %7 = 6

这是预期的。但是修改后的程序没有static_cast

#include <iostream>
using namespace std;

int main()
{
     const size_t DAYS_OF_WEEK = 7;
     unsigned int dayOfFirstDay = 0;
     unsigned int _firstDayOfWeek = 1;
     unsigned int diff = (DAYS_OF_WEEK+ (dayOfFirstDay - _firstDayOfWeek) ) % DAYS_OF_WEEK;
     cout << "diff = ("  << DAYS_OF_WEEK << " + (" << dayOfFirstDay << " - " << _firstDayOfWeek << ")) %" << DAYS_OF_WEEK
         << " = " << diff << endl;
     return 0;
}

输出

diff = (7 + (0 - 1)) %7 = 3

这是意料之外的。为什么?

(在 Ubuntu 64 位上,这两个程序都使用 g++ 9.3.0 编译)

【问题讨论】:

  • DAYS_OF_WEEK 具有不同的类型,因此环绕会导致不同的数字,在两个版本中都打印 DAYS_OF_WEEK+ (dayOfFirstDay - _firstDayOfWeek)。
  • unsigned int 可以拥有的最大数字是4,294,967,295,因此如果您使用unsigned int 作为您的数据类型,0-1 的计算结果就是该值。还有( 7 + ( 4294967295 ) ) % 7 == 3。
  • @alle_meije -- 整数类型可以表示的值范围取决于编译器。 C 和 C++ 标准只要求 unsigned int 足够大以表示从 0 到 65,535 的值,即 16 位。现在许多编译器都支持更大的范围,4,294,967,295 是 32 位无符号整数的上限。上限远大于 64 位无符号整数的上限。您的分析对于 32 位整数是正确的。

标签: c++ static-cast


【解决方案1】:

在您的平台上,size_t 似乎是 64 位,unsigned int 是 32 位。

没有integral promotion 到 64 位1。这就是在表达式中混合 64 位操作数的危险。

因此,当转换为 64 位时,-1 的 32 位环绕保持为 4294967295。

我们得到 7 + 4294967295(以 64 位执行)= 4294967302(无回绕)。

4294967302 % 7 = 3


1 除了 (unsigned) int 本身是 64 位的系统,目前不太可能。

【讨论】:

    【解决方案2】:

    当size_t 的宽度大于unsigned int 时,可能会出现这种结果。

    unsigned int 和 unsigned int 的减法环绕并得到 unsigned int。 0 - 1 产生-1,当unsigned int 为4 字节长时,它可能变成0xffffffff。

    然后,将其与另一个unsigned int 相加将得到unsigned int,因此结果看起来像正常的减法和加法。

    另一方面,与size_t 相加将使其在size_t 域中计算,因此不会发生截断并且值7 + 0xffffffff 将被除以而不是7 - 1。

    这是一个检查除法前值的示例代码:

    #include <iostream>
    #include <ios>
    
    int main()
    {
         const size_t DoW = 7;
         const unsigned int DAYS_OF_WEEK = static_cast<unsigned int> (DoW);
         unsigned int dayOfFirstDay = 0;
         unsigned int _firstDayOfWeek = 1;
         size_t to_add = dayOfFirstDay - _firstDayOfWeek;
         size_t diff_uint = DAYS_OF_WEEK+ (dayOfFirstDay - _firstDayOfWeek);
         size_t diff_sizet = DoW+ (dayOfFirstDay - _firstDayOfWeek);
         std::cout << "sizeof(unsigned int) = " << sizeof(unsigned int) << '\n';
         std::cout << "sizeof(size_t) = " << sizeof(size_t) << '\n';
         std::cout << std::hex;
         std::cout << "to add     : 0x" << to_add << '\n';
         std::cout << "diff_uint  : 0x" << diff_uint << '\n';
         std::cout << "diff_sizet : 0x" << diff_sizet << '\n';
         return 0;
    }
    

    这里是an example of output:

    sizeof(unsigned int) = 4
    sizeof(size_t) = 8
    to add     : 0xffffffff
    diff_uint  : 0x6
    diff_sizet : 0x100000006
    

    【讨论】:

      【解决方案3】:

      dayOfFirstDay - _firstDayOfWeek 是一个unsigned int。由于_firstDayOfWeek 大于dayOfFirstDay,该值是一个下溢并环绕并成为unsigned int 的最大值。我们称这个值为max_uint。

      另一方面,DAYS_OF_WEEK 是一个size_t,它可能比unsigned int 更宽泛。这意味着DAYS_OF_WEEK + max_uint 没有溢出。所以你最终计算了max_uint % 7。但是max_uint % 7 不等于-1 ...

      【讨论】:

        【解决方案4】:

        尝试减少混淆:

        #include <stdio.h>
        #include <stddef.h>
        
        int main() {
          printf("0u - 1u = %u\n", 0u - 1u);
          printf("7u + (0u - 1u) = %u\n", 7u + (0u - 1u));
          printf("7zu + (0u - 1u) = %zu\n", size_t{7} + (0u - 1u));
        }
        

        我得到的输出:

        0u - 1u = 4294967295
        7u + (0u - 1u) = 6
        7zu + (0u - 1u) = 4294967302
        

        如您所见,0u - 1u 导致环绕。将这个巨大的数字添加到unsigned int 会导致另一个环绕。将其添加到 size_t 不会因为整个值是可表示的。因此,在模运算符之后会得到不同的结果。

        【讨论】:

          【解决方案5】:

          在这个声明的初始化表达式中

          unsigned int diff = (DAYS_OF_WEEK+ (dayOfFirstDay - _firstDayOfWeek) ) % DAYS_OF_WEEK;
          

          子表达式(dayOfFirstDay - _firstDayOfWeek)等于unsigned int类型的最大值。

          因此在这个子表达式中DAYS_OF_WEEK 的类型为unsigned int

          (DAYS_OF_WEEK+ (dayOfFirstDay - _firstDayOfWeek) )
          

          发生溢出。

          当DAYS_OF_WEEK 的类型为size_t 且均未发生溢出时。

          这就是结果不同的原因。

          【讨论】:

            猜你喜欢
            • 2018-08-25
            • 1970-01-01
            • 2010-09-13
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-10-17
            • 2010-10-04
            • 2013-11-12
            相关资源
            最近更新 更多