簡體   English   中英

整數算術產生一個奇怪的結果(除法后舍入?)

[英]Integer arithmetic produces a strange result (rounding after division?)

在Linux上使用gcc版本4.8.4,short是16位,int是32位。

#include "stdio.h"
int main( void ){
  unsigned short u = 0xaabb;
  unsigned int   v = 0xaabb;
  printf ("%08x %08x\n", u, (unsigned short)((u*0x10001)/0x100));
  printf ("%08x %08x\n", v, (unsigned short)((v*0x10001)/0x100));
  return 0;
}

結果:

0000aabb 0000bbab
0000aabb 0000bbaa

這可以改變,例如,通過除以0x10,其為第一種情況產生類似的結果(+1)。 如果/0x100截斷的字節小於0x80,則不會產生這種效果。 第一種情況( short u )的機器代碼看起來好像有些舍入(加上0xFF)。

  1. 結果是什么原因還是錯誤?
  2. 其他編譯器的結果是什么?

0x10001這樣的文字將是int類型(如果它可以適合int,在這種情況下為true)。 int是簽名類型。

由於變量u是一個小整數類型,因此只要在表達式中使用,它就會將整數提升為int

0xaabb * 0x10001應該給出結果0xAABBAABB 但是,結果是太大,超出內部int 32位二進制補體系統,其中一個最大數量的int0x7FFFFFFF 所以你得到一個有符號整數的溢出,因此調用未定義的行為 - 任何事情都可能發生。

在進行任何形式的二進制算術時,切勿使用有符號整數!

此外,最終轉換為(unsigned short)是徒勞的,因為printf參數無論如何都會將傳遞的值提升為int 嚴格來說這也是不正確的,因為%x意味着printf需要unsigned int

為了避免C中不可預測和有限的默認整數類型的所有問題,請改用stdint.h 此外,使用unsigned int literals解決了許多隱式類型提升錯誤。

例:

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

int main( void ){
  uint16_t u = 0xaabb;
  uint16_t v = 0xaabb;
  printf ("%08" PRIx16 " %08" PRIx16 "\n", u, (uint16_t)(u*0x10001u/0x100u));
  printf ("%08" PRIx16 " %08" PRIx16 "\n", v, (uint16_t)(v*0x10001u/0x100u));
  return 0;
}

(此代碼也有參數提升,但是通過使用PRIx16格式說明符,您告訴printf現在編譯器的業務是使代碼工作,而不管函數調用中可能存在哪種類型的促銷。)

通常的算術轉換

u在乘法之前轉換為int 由於int是有符號的,因此它在除法上的行為不同。

printf("%08x\n", (u*0x10001)/0x100);
printf("%08x\n", (v*0x10001)/0x100);

返回

ffaabbab
00aabbaa

嚴格來說,有符號整數上的乘法溢出已經是未定義的行為,因此即使在除法之前結果也是無效的。

u*0x10001的結果是int =導致有signed類型的溢出,從而導致未定義的行為。

假定16位short和32位int (典型的x86,ARM和大多數其他32位系統):

您的代碼中有兩種類型的未定義行為 (UB)。 首先,在格式字符串中使用錯誤的類型說明符。 %x期望unsigned int ,而將unsigned short擴展傳遞給signed int

第二個 - 你在這里看到的是第一個計算: u被轉換為int (整數提升) - 不是unsigned int用於乘法,因為常量0x10001也是int 乘法調用UB,因為它生成有符號整數溢出。 一旦你調用UB,你就會迷失方向,任何進一步的解釋都是無用的。

說,我們現在推測:發生的是,在乘法之后,你可能有一個負值,並且隨着除法向零舍入(這是標准要求),你得到更高的負值。 但是當你打印為無符號時,你會看到一個更大的原始(無符號)值。 這是因為負值的2的補碼內部表示。


請注意,此結果超出了C標准。 事實上,編譯器可能會生成代碼來格式化您的硬盤驅動器,或者您的計算機可能會跳出窗口或出現鼻子守護程序 所以,糾正錯誤:

  • 使用%hx打印unsigned short int
  • 例如,使用u * 0x10001U強制轉換為unsigned int進行乘法。 通常,如果使用無符號值,建議始終使用U (無符號)后綴。

我稍微擴展了你的代碼來解釋:

#include "stdio.h"
int main( void ){
  unsigned short u = 0xaabb;
  unsigned int   v = 0xaabb;

  printf ("not casted:\n");
  printf ("%08x %08x\n", u, ((u*0x10001)/0x100));
  printf ("%08x %08x\n", v, ((v*0x10001)/0x100));

  printf ("unsigned short casted:\n");
  printf ("%08x %08x\n", u, (unsigned short)((u*0x10001)/0x100));
  printf ("%08x %08x\n", v, (unsigned short)((v*0x10001)/0x100));

  printf ("u*0x10001:\n");
  printf ("x=%08x d=%d\n", u*0x10001, u*0x10001);

  // Solution
  printf ("Solution:\n");
  printf (">>> %08x %08x\n", u, (unsigned short)((u*0x10001UL)/0x100UL));
  printf (">>> %08x %08x\n", v, (unsigned short)((v*0x10001UL)/0x100UL));
  return 0;
}

這導致以下輸出:

not casted:
0000aabb ffaabbab
0000aabb 00aabbaa
unsigned short casted:
0000aabb 0000bbab
0000aabb 0000bbaa
u*0x10001:
x=aabbaabb d=-1430541637
Solution:
>>> 0000aabb 0000bbaa
>>> 0000aabb 0000bbaa

所以你看到操作u*0x10001將生成一個有signed int (32位)值,因此你的結果是d=-1430541637 如果將此值除以0x100您將得到0xFFAABBAB的結果。 如果您使用unsigned short轉換此值,則得到結果= 0x0000BBAB 如果要防止這種情況,編譯器會對此操作使用無符號值,則必須將UL編寫為數字的擴展名。

所以你看到編譯器正在按預期工作。 你可以在這里自己編譯代碼[^]

暫無
暫無

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

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