[英]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 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;
}
這是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_WEEK
是size_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.