2011-08-28 50 views
69

我遇到過來自某個人的代碼,這個代碼似乎認爲當結果是否定的時候,從另一個相同類型的整數中減去一個無符號整數是有問題的。因此,即使這種代碼恰好適用於大多數架構,這樣的代碼也是不正確的。是無符號整數減法定義的行爲?

unsigned int To, Tf; 

To = getcounter(); 
while (1) { 
    Tf = getcounter(); 
    if ((Tf-To) >= TIME_LIMIT) { 
     break; 
    } 
} 

這是我能找到的C標準中唯一含糊不清的相關引用。

涉及無符號的操作數的一種計算可以從未溢流,因爲一個 結果不能由所得到的無符號整數 類型來表示減小模比可以由表示的最大 值大一個數量結果類型。

我想可以採用這個引用來表示當右操作數較大時,操作被調整爲在模截斷數的上下文中是有意義的。

0×0000 - 0×0001 == 0X 0000 - 0×0001 == 0xFFFF的

,而不是使用實施依賴簽名的語義:

0×0000 - 0×0001 ==(無符號) (0 + 1)==(0xFFFF,但同時也0xFFFE或在0x8001)

哪個或哪些解釋是正確的?它是否定義?

+3

標準中詞的選擇是不幸的。它「永遠不會溢出」意味着它不是一個錯誤情況。使用標準中的術語,而不是溢出值「包裝」。 – danorton

回答

81

減法生成的無符號類型負數的結果是明確的:

  1. [...]涉及無符號的操作數的一種計算可以永遠不會溢出,因爲 一個結果是不能由所得到的無符號整數類型表示爲 減少的模數大於可由最終類型表示的最大值 的數。 (ISO/IEC 9899:1999(E)§6.2.5/ 9)

正如你所看到的,(unsigned)0 - (unsigned)1等於-1模UINT_MAX + 1,或者換句話說,UINT_MAX。

注意的是,雖然它說:「在無符號操作數A計算可以永遠不會溢出」,這可能會導致您認爲它僅適用於超出上限,這是作爲一個動機的實際結合部分的句子:「無法用結果的無符號整數類型表示的結果是 減少的模數,該數大於可由最終類型表示的最大值 」。這個短語不限於類型上限的溢出,並且同樣適用於太低而不能表示的值。

+2

謝謝!我現在看到我錯過的解釋。我認爲他們本可以選擇更清晰的措辭。 – jbcreix

+1

現在我感覺好多了,知道如果任何無符號的加法滾到零並導致混亂,那將是因爲'uint'總是用來表示數學[ring](https://en.wikipedia.org/維基/ Ring_(數學))的整數'0'到'UINT_MAX',加法和乘法運算'UINT_MAX + 1',而不是因爲溢出。然而,它確實存在這樣的問題:如果環是這樣一個基本的數據類型,那麼該語言不會爲其他尺寸的環提供更多的普遍支持。 –

+0

@TheodoreMurdock我認爲這個問題的答案很簡單。據我所知,這是一個環是一個結果,而不是一個原因。真正的要求是無符號類型必須具有參與值表示的所有位。環狀行爲自然流動。如果你想從其他類型的行爲,然後做你的算術,然後應用所需的模數;使用基本操作員。 –

91

當你與無符號類型,modular arithmetic工作(也被稱爲「環繞」行爲)正在發生。爲了理解這一點模算術,只是看一下這些時鐘:

enter image description here

9 + 4 = 113模12),因此對其他方向,它是:1 - 4 = 9-3 mod 12)。在處理無符號類型時應用相同的原則。如果結果類型unsigned,則發生模運算。


現在來看看下面的操作存儲結果作爲unsigned int

unsigned int five = 5, seven = 7; 
unsigned int a = five - seven;  // a = (-2 % 2^32) = 4294967294 

int one = 1, six = 6; 
unsigned int b = one - six;   // b = (-5 % 2^32) = 4294967291 

如果你想確保結果是signed,然後將其存儲到signed變量或將其轉換爲signed 。如果你想獲得的數字之間的差異,並確保模算術都不會被應用,那麼你應該考慮使用stdlib.h定義abs()功能:

int c = five - seven;  // c = -2 
int d = abs(five - seven); // d = 2 

要非常小心,尤其是在寫作的條件,這是因爲:

if (abs(five - seven) < seven) // = if (2 < 7) 
    // ... 

if (five - seven < -1)   // = if (-2 < -1) 
    // ... 

if (one - six < 1)    // = if (-5 < 1) 
    // ... 

if ((int)(five - seven) < 1) // = if (-2 < 1) 
    // ... 

if (five - seven < 1) // = if ((unsigned int)-2 < 1) = if (4294967294 < 1) 
    // ... 

if (one - six < five) // = if ((unsigned int)-5 < 5) = if (4294967291 < 5) 
    // ... 
+2

與時鐘很好,但_proof_會使這是正確的答案。這個問題的前提已經包含了所有這一切都可能是真實的斷言。 –

+4

@LightnessRacesinOrbit:謝謝。我寫這篇文章是因爲我認爲有人會覺得它非常有幫助。我同意,這不是一個完整的答案。 – LihO

+0

無論如何,你有一個upvote,因爲我同意;) –

3

好,第一種解釋是正確的。然而,你在這種情況下對「簽名語義」的推理是錯誤的。

同樣,你的第一個解釋是正確的。無符號算術遵循模算術的規則,即對於32位無符號類型,0x0000 - 0x0001的計算結果爲0xFFFF

但是,第二種解釋(基於「簽名語義」的解釋)也需要產生相同的結果。即即使您在簽名類型的域中評估0 - 1,並獲得-1作爲中間結果,但在稍後將其轉換爲無符號類型時,仍需要此-1才能生成0xFFFF。即使某些平臺對有符號整數使用了奇異表示(1的補碼,有符號數量級),該平臺仍然需要在將有符號整數值轉換爲無符號整數值時應用模運算規則。

例如,這種評價

signed int a = 0, b = 1; 
unsigned int c = a - b; 

仍保證生產UINT_MAXc,即使該平臺採用異國情調的代表符號整數。

+1

我認爲你的意思是16位無符號類型,而不是32位。 – xioxox

2

隨着unsigned int型或更大的無符號數,在沒有類型轉換的,a-b被定義爲產生的無符號數,當加入到b,將產生a。將負數轉換爲無符號被定義爲產生一個數字,該數字在添加到符號反轉的原始數字時將產生零(因此將-5轉換爲無符號將產生一個值,該值在添加到5時將產生零) 。

注意:不是unsigned int較小的無符號數可能會促進減法之前鍵入inta-b行爲將取決於int大小。

相關問題