[英]Adding int type to uint64_t c++
我有一個關於整數轉換的問題:
#include <iostream>
#include <cstdint>
using namespace std;
int main()
{
int N,R,W,H,D;
uint64_t sum = 0;
uint64_t sum_2 = 0;
cin >> W >> H >> D;
sum += static_cast<uint64_t>(W) * H * D * 100;
sum_2 += W * H * D * 100;
cout << sum << endl;
cout << sum_2 << endl;
return 0;
}
我認為,該sum
應該等於sum_2
,因為uint64_t
類型大於int
類型,並且在算術運算期間編譯器選擇更大的類型(即 uint64_t)。 所以據我了解, sum_2
必須具有uint64_t
類型。 但它有 int 類型。
你能解釋一下為什么 sum_2 被轉換為int
嗎? 為什么不保持uint64_t
?
如果我為W
、 H
和D
輸入200
、 300
和358
,我會得到以下 output ,這對於我在 64 位 ZEDC9F11356BD44120AF49CC96C9DCF3B3Z 編譯器上非常有意義
2148000000
18446744071562584320
為什么這很有意義?
嗯,默認類型是int
,對於 64 位 Linux 機器上的 gcc 編譯器來說,它是int32_t
,它的最大值是 2^32/2-1 = 21474847447,它的最小值是 -21.4 行sum_2 += W * H * D * 100;
做int
算術,因為那是那里每個變量的類型,包括100
,並且沒有使用顯式轉換。 因此,在執行int
算術之后,它會將int
結果隱式轉換為uint64_t
,因為它將結果存儲到uint64_t sum_2
變量中。 然而,在該點之前右側的int
算術會導致2148000000
,它具有未定義的行為,簽名 integer 溢出到最大int
值的頂部並返回到最小int
值並再次上升。
Even though according to the C and C++ standards, signed integer overflow or underflow is undefined behavior , in the gcc
compiler, I know that signed integer overflow happens to roll over to negative values if it is not optimized out. 默認情況下,這仍然是“未定義的行為”,並且是一個錯誤,默認情況下不得依賴。 有關如何通過 gcc 擴展實現此明確定義的行為的詳細信息和信息,請參閱下面的注釋。 無論如何,2148000000 - 2147483647 = 516353 向上計數,其中第一個導致翻轉。 第一個計數累加到最小int32_t
值 -2147483648,下一個 (516353 - 1 = 516352) 計數 go 直到 -2147483648 + 516352 = -2146967296。 因此,基於未定義行為,上述輸入的W * H * D * 100
的結果現在是-2146967296
。 接下來,將該值從int
(在本例中為int32_t
)隱式轉換為uint64_t
,以便將其從int
(在本例中為int32_t
)存儲到uint64_t sum_2
變量中,從而導致明確定義的行為 unsigned integer underflow 。 你從 -2146967296 開始。 第一個下調下降到uint64_t
MAX,它是2^64-1 = 18446744071562584320
。
瞧,有了一點編譯器和硬件架構的了解,以及一些預期但未定義的行為,結果是完全可以解釋的並且是有道理的!
要輕松查看負值,請將其添加到您的代碼中:
int sum_3 = W*H*D*100;
cout << sum_3 << endl; // output: -2146967296
永遠不要故意在代碼中留下未定義的行為。 這被稱為錯誤。 您不必編寫 ISO C++,但是,如果您能找到編譯器文檔表明某種行為是明確定義的,那沒關系,只要您知道您使用的是 g++ 語言而不是 ZF6F87C3FCF8B3C2C297 語言。 並且不要期望您的代碼在編譯器中也能同樣工作:這是我這樣做的一個示例:在 C 中使用 Unions 進行“類型雙關”很好,在 gcc 的 ZF6F87C9FDCF8B3C3F07F93F1EE8712C9++Z 中也很好(作為 ZE03AZ4[1113696DCF31] 擴展名) ) . 我通常可以依賴這樣的編譯器擴展。 只是要知道你在做什么。
@user17732522 在這里的評論中提出了一個很好的觀點:
“在 gcc 編譯器中,我知道簽名的 integer 溢出恰好會翻轉為負值。” : 默認情況下這是不正確的。 默認情況下 GCC 假定不會發生有符號溢出並基於此應用優化。 有
-fwrapv
和/或-fno-strict-overflow
標志來強制包裝行為。 請參閱https://gcc.gnu.org/onlinedocs/gcc-12.1.0/gcc/Code-Gen-Options.html#Code-Gen-Options 。
看看上面的那個鏈接(或者更好的是,這個鏈接總是指向最新的 gcc 文檔而不是一個版本的文檔: https://gcc.gnu.org/onlinedocs/gcc/Code-Gen- Options.html#Code-Gen-Options )。 即使根據 C 和 C++ 標准,有符號整數上溢和下溢是未定義的行為(一個錯誤),gcc 允許通過擴展來定義正確的行為。 gcc 構建標志。 使用-fwrapv
可以將有符號整數上溢/下溢明確定義為 gcc 擴展。 此外, -fwrapv-pointer
允許指針在用於指針算術時安全上溢和下溢,而-fno-strict-overflow
同時適用-fwrapv
和-fwrapv-pointer
。 相關文檔在這里: https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html#Code-Gen-Options (強調添加):
這些與機器無關的選項控制代碼生成中使用的接口約定。
大部分都有正負forms;
-ffoo
的否定形式是-fno-foo
。...
-fwrapv
此選項指示編譯器假設加法、減法和乘法的有符號算術溢出使用二進制補碼表示來回繞。 此標志啟用一些優化並禁用其他優化。 選項-ftrapv
和-fwrapv
覆蓋,因此在命令行上使用-ftrapv -fwrapv
會導致-fwrapv
有效。 請注意,只有活動選項會覆蓋,因此在命令行上使用-ftrapv -fwrapv -fno-wrapv
會導致-ftrapv
有效。
-fwrapv-pointer
此選項指示編譯器假設加法和減法上的指針算術溢出使用二進制補碼表示來回繞。 此標志禁用一些假設指針溢出無效的優化。
-fstrict-overflow
此選項暗示-fno-wrapv -fno-wrapv-pointer
並且當否定時 [as-fno-strict-overflow
] 暗示-fwrapv -fwrapv-pointer
。
因此,依賴有符號整數溢出或下溢而不使用上面正確的 gcc 擴展標志是未定義的行為,因此是一個錯誤,不能安全地依賴。 如果沒有上面的 gcc 擴展標志,它可能會被編譯器優化,並且無法按預期可靠地工作。
這是我用於快速檢查以編寫此答案的總代碼。 我在 64 位 Linux 機器上使用 gcc/g++ 編譯器運行它。 我沒有使用-fwrapv
或-fno-strict-overflow
標志,所以下面展示的所有簽名的 integer 上溢或下溢都是未定義的行為,一個錯誤,如果沒有這些 gcc 擴展標志,就無法安全地依賴它。 它工作的事實是間接的,因為默認情況下,編譯器可以選擇以意想不到的方式優化溢出。
如果您在 8 位微控制器(例如 Arduino Uno)上運行它,您會得到不同的結果,因為默認情況下int
是 2 字節int16_t
,但是,現在您了解了原理。 您可以找出預期的結果,(另外,我認為該架構上不存在 64 位值。因此它們變為 32 位值)。
#include <iostream>
#include <cstdint>
using namespace std;
int main()
{
int N,R,W,H,D;
uint64_t sum = 0;
uint64_t sum_2 = 0;
// cin >> W >> H >> D;
W = 200;
H = 300;
D = 358;
sum += static_cast<uint64_t>(W) * H * D * 100;
sum_2 += W * H * D * 100;
cout << sum << endl;
cout << sum_2 << endl;
int sum_3 = W*H*D*100;
cout << sum_3 << endl;
sum_2 = -1; // underflow to uint64_t max
cout << sum_2 << endl;
sum_2 = 18446744073709551615ULL - 2146967295;
cout << sum_2 << endl;
return 0;
}
只是@Gabriel Staples好答案的簡短版本。
“並且在算術運算期間編譯器選擇更大的類型(即 uint64_t)”
W * H * D * 100
中沒有uin64_t
,只有四個int
。 在這個乘法之后, int
乘積(溢出並且是 UB)被分配給一個uint64_t
。
相反,使用100LLU * W * H * D
來執行更廣泛的無符號乘法。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.