簡體   English   中英

為什么在 C++ 中混合 size_t 和 unsigned int 的模除法 go 錯誤

[英]Why does modulo division go wrong for mix of size_t and unsigned int in C++

給定一個程序

#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;
}

該程序的 output 是

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 編譯的)

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

沒有對 64 位1整體提升 這就是在表達式中混合 64 位操作數的危險。

因此,當轉換為 64 位時,-1 的 32 位環繞保持為 4294967295。

我們得到 7 + 4294967295(以 64 位執行)= 4294967302(無環繞)。

4294967302 % 7 = 3


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

size_t的寬度大於unsigned int時,可能會發生這種結果。

unsigned intunsigned 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;
}

這是output 的示例

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

dayOfFirstDay - _firstDayOfWeek是一個unsigned int 由於_firstDayOfWeek大於dayOfFirstDay ,該值是下溢並環繞並成為unsigned int的最大值。 我們稱這個值為max_uint

另一方面DAYS_OF_WEEKsize_t ,它可能是比unsigned int更寬的類型。 這意味着DAYS_OF_WEEK + max_uint沒有溢出。 所以你最終計算max_uint % 7 但是max_uint % 7等於-1 ...

嘗試減少混淆:

#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));
}

output 我得到:

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

如您所見, 0u - 1u導致環繞。 將這個巨大的數字添加到unsigned int會導致另一個環繞。 將其添加到size_t並不是因為整個值是可表示的。 出於這個原因,在模運算符之后會得到不同的結果。

在此聲明的初始化表達式中

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且均未發生溢出時。

這就是結果不同的原因。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM