我曾經寫過一個顯示int類型數據二進制表示(補碼)的小程序,代碼如下:
#include <limits.h> #include <stdio.h>
int main(void) { int i = 0;int value = -128; // 0xffffff80 int shift = INT_MIN; // 0x80000000
printf("The decimal value is: %d\n\n", value);
printf("* * * *\n");// print marks
for (i = 0; i < 32; ++i) { if (shift & value) { printf("1");} else { printf("0");}
shift = shift >> 1; // right shift 1 bit } // print bits
return 0;}預期的輸出如下:
The decimal value is: -128
* * * * 11111111111111111111111110000000然而實際的輸出如下:
The decimal value is: -128
* * * * 11111111111111111111111111111111追蹤變量shift的值,發現其最高位(MSB)始終為1,我恍然大悟:這貨是算術右移,也就是說會保留符號位。於是,將shift改為(unsigned int)類型,果然解決了這個bug.說明,對於無符號數,進行的是邏輯右移,在移走的位上填0.
下面我們自然會聯想到,是不是無符號數的左移為邏輯左移,而有符號數的左移為算術左移(即保留符號位)呢?答案是非也,事實上邏輯左移與算術左移是相同的,都不會保留符號位。
右移運算的結果是不會超出表示范圍的。對於有符號數,右移一位後符號位空出,則必須保留原符號位的值;對於無符號數,沒有符號位,自然采取邏輯右移。
左移運算則不同,它是有可能超出表示范圍的,也就是溢出。對於MSB位為0、MSB-1位為1的有符號正數,及MSB位為1、MSB-1位為0的有符號負數,左移必將益出(以int類型為例,其表示范圍是-2^31~2^31-1,對於大於等於2^30和小於等於-2^30-1的值左移1位,也就是乘以2,必然超出了表示范圍),且只有在這些情況下符號位才發生變化。既然已經溢出了,保留原符號位還有什麼意義?
綜上,C語言中可以進行位運算的char/short/int/long (int)/long long (int)類型是有符號的,加上unsigned前綴則是無符號的,兩者在右移運算上是有區別的。