簡體   English   中英

在C中聲明64位變量

[英]Declaring 64-bit variables in C

我有個問題。

uint64_t var = 1; // this is 000000...00001 right?

在我的代碼中,這有效:

var ^ (1 << 43)

但它怎么知道1應該是64位? 我不應該寫這個嗎?

var ^ ( (uint64_t) 1 << 43 )

正如你所想的那樣,1是一個普通的有符號int (在你的平台上可能是2位補碼算法中的32位寬),所以是43,所以1<<43導致溢出:事實上,如果兩個參數都是類型為int運算符規則表明結果也是int

仍然,在C簽名整數溢出是未定義的行為,所以在原則上任何事情都可能發生。 在你的情況下,編譯器可能會發出代碼來在64位寄存器中執行該移位,所以幸運的是它似乎可行; 要獲得保證正確的結果,您應該使用您編寫的第二種形式,或者,替代地,使用ull后綴將unsigned long long文本指定為1unsigned long long保證至少為 64位)。

var ^ ( 1ULL << 43 )

我推薦OP的方法,施放常數( (uint64_t) 1 << 43 )

對於OP的小例子,下面的2 可能會執行相同的操作。

uint64_t var = 1; 
// OP solution)
var ^ ( (uint64_t) 1 << 43 )
// Others suggested answer
var ^ ( 1ULL << 43 )        

以上結果具有相同的值 ,但是類型不同 潛在的區別在於C中有兩種類型: uint64_tunsigned long long以及可能跟隨的內容。

uint64_t的精確范圍為0到2 64 -1。
unsigned long long的范圍為0到至少為 2 64 -1。

如果unsigned long long 總是 64位,因為它似乎在很多機器上都存在,沒有問題,但讓我們展望未來,並說這段代碼是在unsigned long long為16字節的機器上運行的(0到至少 2 128 -1)。

下面一個人為的例子:的第一個結果^是一個uint64_t ,當乘以3,產品仍會被uint64_t ,執行模2 64,應該發生溢出 ,則該結果被分配到d1 在下一種情況下, ^的結果是unsigned long long ,當乘以3時,乘積可能大於2 64 ,然后將其分配給d2 所以d1d2有不同的答案。

double d1, d2;
d1 = 3*(var ^ ( (uint64_t) 1 << 43 ));
d2 = 3*(var ^ ( 1ULL << 43 ));

如果想要使用unit64_t ,請保持一致。 不要假設unit64_tunsigned long long是相同的。 如果你的回答是unsigned long long ,那很好。 但根據我的經驗,如果開始使用固定大小的類型,如uint64_t ,則不希望變體大小類型弄亂計算。

var ^ ( 1ULL << 43 )應該這樣做。

使unit64_t常量的可移植方法是使用UINT64_C宏(來自stdint.h ):

UINT64_C(1) << 43

很可能UINT64_C(c)被定義為類似c ## ULL東西。

從C標准:

INT N _C(value)將擴展為對應於int_least N _t類型的整數常量表達式。 UINTN_ C (value)將擴展為對應於類型uint_least N _t的整數常量表達式。 例如,如果uint_least64_tunsigned long long int類型的名稱,則UINT64_C(0x123)可能會擴展為整數常量0x123ULL

您的編譯器不知道轉換應該以64位完成。 但是,對於此特定代碼的特定配置中的這個特定版本的編譯器,兩個錯誤恰好是正確的。 不要指望它。

假設int在您的平台上是32位類型(很可能), 1 << 43中的兩個錯誤是:

  • 如果移位量大於或等於左操作數類型的寬度,則行為未定義。 這意味着,如果x是類型的intunsigned int ,然后x << 43有未定義的行為,如同x << 32或任何其他x << n其中n≥32。例如1u << 43將具有未定義行為太。
  • 如果左操作數具有簽名類型,並且操作的結果溢出該類型,則行為未定義。 例如0x12345 << 16具有未定義的行為,因為左操作數的類型是有符號類型int但結果值不適合int 另一方面, 0x12345u << 16是明確定義的,其值為0x23450000u

“未定義的行為”意味着編譯器可以自由地生成崩潰或返回錯誤結果的代碼。 碰巧在這種情況下你獲得了理想的結果 - 這不是禁止的,但墨菲的法則規定有一天生成的代碼不會做你想要的。

為了保證操作發生在64位類型上,您需要確保左操作數是64位類型 - 您分配結果的變量類型無關緊要。 這與float x = 1 / 2問題相同,導致x包含0而不是0.5:只有操作數的類型才能確定算術運算符的行為。 任何(uint64)1 << 43(long long)1 << 43(unsigned long long)1 << 43 1ll << 431ull << 431ull << 43都可以。 如果使用帶符號類型,則僅在沒有溢出時定義行為,因此如果您希望在溢出時截斷,請確保使用無符號類型。 通常建議使用無符號類型,即使由於行為是可重現的而不應發生溢出 - 如果使用帶符號類型,那么僅僅為了調試目的而打印輸出值的行為可能會改變行為(因為編譯器喜歡利用未定義的行為生成任何代碼在微觀層面上最有效,這對諸如寄存器分配壓力之類的事情非常敏感。

由於您希望結果為uint64_t類型,因此使用該類型執行所有計算會更清楚。 從而:

uint64_t var = 1;
… var ^ ((uint64_t)1 << 43) …

暫無
暫無

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

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