簡體   English   中英

轉換有符號和無符號數字

[英]Casting signed and unsigned numbers

我有一個 function 在將有符號數除以無符號數時產生無效輸入。 這意味着負數被解釋為unsigned數,因此計算無效。 例如,以下是在兩個可能有符號的數字之間進行划分時的可能性:

printf ("%d %d %d %d %d %d %d %d %d",
        (signed)10 / (signed)8,
        (unsigned)10 / (signed)8,
        (unsigned)10 / (unsigned)8,
        
        (signed)-10 / (signed)8,
        (unsigned)-10 / (signed)8,       // bad result
        (unsigned)-10 / (unsigned)8,     // bad result
        
        (signed)-10 / (signed)-8,
        (unsigned)-10 / (signed)-8,     // ??
        (unsigned)-10 / (unsigned)-8    // ??
);

$ run
// 1 1 1 -1 536870910 536870910 1 0 0

signed數除以unsigned數時的規則是什么? 在計算之前,兩個數字是否都轉換為相同的類型? 或者結果是什么? 似乎在 function 中我有一個帶符號的負數被一個無符號的正數除,我認為這會“正常工作”,但似乎它們而不是隱式轉換的類型,因此我的數字將是,例如, "-1", 結果是一個很大的正數。 對混合符號數進行運算時的規則是什么?

對於 arguments 中的每一個,按此順序發生三件事:

  1. 執行強制轉換指定的轉換。
  2. /的兩個操作數被轉換為通用類型。
  3. 參數傳遞給printf以由%d轉換為文本。

讓我們按順序討論這些。

1. 演員表和轉換

轉換是價值從一種類型到另一種類型的變化。 在可能的情況下,轉換不會改變價值。 否則,我們希望轉換盡可能少地改變值(將 3.125 從float轉換為int產生 3,而不是 87)或以某種信息保留方式(從 32 位int轉換為 32 位unsigned int “wrap ” 模 2 32 )。

一些轉換會自動發生。 例如int x = 3.125f; float值 3.125 轉換為int值 3。

為了顯式請求轉換,我們使用強制轉換。 ) .強制轉換運算符具有形式( ) 演員表就是這個運算符。 自動轉換不是強制轉換。

當帶有值x的有符號 integer 轉換為n位無符號 integer 時,結果是,通過取x並加上或減去 2 n得到的結果,直到得到一個可在無符號 Z157DB7DF3360023 類型中表示的值。 例如,當 -10 轉換為 32 位unsigned int時,將 2 32 (4,294,967,296) 添加一次,產生 -10 + 4,294,967,296 = 4,294,967,286。 轉換 -8 時,結果為 -8 + 4,294,967,296 = 4,294,967,288。

這兩個都必須添加一次 2 32才能使值在范圍內。 要查看需要多次加法或減法的示例,請考慮將 1001 轉換為八位unsigned char 256 的三個減法對於將結果帶入 0 到 255 范圍內是必要的,因此結果為 1001 - 3•256 = 1001 - 768 = 233。

2. 通常的算術轉換

對於許多 C 二元運算符,特別是算術運算符,將兩個操作數轉換為通用類型執行操作。 (這主要是由於計算機硬件的工作原理;將兩個無符號數或兩個有符號數相乘很容易,但將一個有符號數乘以一個無符號數則有些尷尬。)這種規則稱為通常的算術轉換

例如,C 2018 6.5.5 3 中/運算符的規范說“通常的算術轉換是在操作數上執行的”。 通常的算術轉換在 C 2018 6.3.1.8 中指定。 規則用 rank 和其他細節的技術概念表示,但可以針對標准實類型進行總結:如果任一操作數在下面的列表中比int更早,則將其轉換為int (這部分稱為integer 促銷。)之后,如果兩個操作數類型不同,則將較早類型的操作數轉換為較晚類型:

  • _Bool 、有signed charcharunsigned charshortunsigned shortintunsigned intlongunsigned longlong longunsigned long longfloatdoublelong double

以下是這如何影響您的示例,用 32 位int (又名signed )和 32 位unsigned (又名unsigned int )演示:

  • (signed)10 / (signed)8 兩個操作數都是int 計算 10/8,結果為 1。
  • (unsigned)10 / (signed)8 (signed)8轉換為unsigned ,這不會改變值。 計算 10/8,結果為 1。
  • (unsigned)10 / (unsigned)8 兩個操作數都是unsigned int 計算 10/8,結果為 1。
  • (signed)-10 / (signed)8 兩個操作數都是int 計算-10/8,結果為-1。
  • (unsigned)-10 / (signed)8 (unsigned)-10是 4,294,967,286。 (signed)8轉換為unsigned int ,這不會更改值。 計算 4,294,967,286/8,結果為 536,870,910。
  • (unsigned)-10 / (unsigned)8 (unsigned)-10是 4,294,967,286。 兩個 perand 都是unsigned int 計算 4,294,967,286/8,結果為 536,870,910。
  • (signed)-10 / (signed)-8 兩個操作數都是int 計算-10/-8,結果為1。
  • (unsigned)-10 / (signed)-8 (unsigned)-10是 4,294,967,286。 (signed)−8轉換為unsigned int ,這會將值更改為 4,294,967,288。 計算 4,294,967,286/4,294,967,288,結果為 0。
  • (unsigned)-10 / (unsigned)-8 (unsigned)-10是 4,294,967,286。 (unsigned)−8是 4,294,967,288。 計算 4,294,967,286/4,294,967,288,結果為 0。

3. printf轉換說明符

除法運算符/的結果與通常算術轉換后的操作數具有相同的類型。 所以你的一些 arguments 有int類型,有些有unsigned int類型。

%d用於printf格式時,對應的參數應該是一個int 這是在 C 2018 7.21.6.1 8 中指定的(大約fprintf ,但fprintf子句引用了它)。 第 9 段說“……如果任何參數不是相應轉換規范的正確類型,則行為未定義。”

因此,正式地,您的程序的行為不是由 C 標准定義的。 然而,C 實現經常讓這種用unsigned int參數替換int參數滑動,並且由於歷史原因以及軟件和硬件如何工作的組合,程序“工作”。 (This substitution is explicitly allowed when calling a function declared without a prototype, in C 2018 6.5.2.2 6. One might argue that if you did not include <stdio.h> and declared printf yourself with int printf(); , it would被該規則允許。)

因此,盡管 C 標准未定義行為,但您的程序打印上述結果也就不足為奇了。 但是,對於參數為unsigned int的情況,最好將%d更改為%u

使用(unsigned)-10 ,您在編譯時得到的是4294967295 (uint32_t max value, due to the underflow), minus 10 ,然后除以 8,這確實是 536870910。

至於您的不同部門,結果是標准定義的(信用@Barmar)

暫無
暫無

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

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