讀古今文學網 > C語言解惑 > 17.1 位運算典型錯誤 >

17.1 位運算典型錯誤

【例17.1】程序員想編寫如下程序實現快速運算2*2+1=5,能實現嗎?


#include <stdio.h>
int main
()
{
      int x=1
,y
;
      y=x<<2+1
;
      printf
(\"y=%dn\"
, y
);
      return 0
;
}
  

【解答】不能。「+」運算符的優先級高於「<<」。「y=x<<2+1;」與語句


y=x<<
(2+1
);
  

等效,結果為8。要想使結果為5,應保證執行順序,即修改為


y=
(x<<2
)+1
;
  

【例17.2】下面程序使用一個變量保存8個權限標識。原意是要為指定的用戶設置「P_ADMIN」和備份「P_BACKUP」兩個特權,然後驗證是否正確設置數據,但卻沒有事先預定設想,為什麼會發生這種情況?


#include <stdio.h>
#define CI const int
CI P_USER=
(1<<1
);
CI P_REBOOT=
(1<<2
);
CI P_KILL=
(1<<3
);
CI P_TAPE=
(1<<4
);
CI P_RAW=
(1<<5
);
CI P_DRIVER=
(1<<6
);
CI P_ADMIN=
(1<<7
);
CI P_BACKUP=
(1<<8
);
int main
()
{
    unsigned char privs = 0
;
    privs |= P_ADMIN
;
    privs |= P_BACKUP
;
    printf
(\"Privileges
: \"
);
    if 
(( privs & P_ADMIN
) 
!=0 
)
             printf
(\"Administration \"
);
    if 
(( privs & P_BACKUP
) 
!=0 
)
             printf
(\"Backup \"
);
    printf
(\"n\"
);
    return 0
;
}
  

【解答】一個字符有0到7位(共有8位),用常量(1<<0)到(1<<7)來表示8位,在使用1<<8則超出一個字符位的範圍。所以語句


CI P_BACKUP=
(1<<8
);
  

的定義無效,表達式


privs |= P_BACKUP
;
  

是無效的,結果只是設置了系統管理員權限,運行結果為:


Privileges
: Administration
  

【例17.3】找出下面程序中的錯誤。


#include <stdio.h>
struct status {
    int on_line
:1
;
    int ready
:1
;
    int paper_out
:1
;
    int manual_feed
:1
;
}
;
int main
()
{
    struct status printer_status
;
    printer_status.on_line = 1
;
    if
( printer_status.on_line == 1
)
           printf
(\"Printer is on_line.n \"
);
    else
           printf
(\"Printer down.n \"
);
    return 0
;
}
  

【解答】帶符號位的1位數字可能是0,也可能是-1。由於語句


printer_status.on_line = 1
;
  

的1位長的字段不可能保存值1而失敗(因它的值出現溢出並將變量賦值為-1),結果就導致判斷語句「if(printer_status.on_line==1)」的失敗,輸出「Printer down.」。

單字節字段應該是無符號型的,將它們使用unsigned定義。


//
修改的程序
#include <stdio.h>
struct status {
       unsigned int on_line
:1
;
       unsigned int ready
:1
;
       unsigned int paper_out
:1
;
       unsigned int manual_feed
:1
;
}
;
int main
()
{
     struct status printer_status
;
     printer_status.on_line = 1
;
     if
( printer_status.on_line == 1
)
             printf
(\"Printer is on_line.n \"
);
     else
             printf
(\"Printer down.n \"
);
     return 0
;
}
  

【例17.4】分析下面程序的輸出結果。


#include <stdio.h>
int main
()
{
      char ch = \'A\'
;
      printf
(\"%c \"
, 
(ch | 0x0
));
      printf
(\"%c \"
, 
(ch | 0x4
));
      printf
(\"%c \"
, ch+2
);
      printf
(\"%cn\"
, ch+4
);
      return 0
;
}
  

【分析】第1條打印語句輸出字符A,第2條等效於第4條,都是輸出字符E,第3條輸出字符C。輸出結果為「A E C E」。

【例17.5】分析下面程序的輸出結果。


#include <stdio.h>
int main
()
{
     int flags = 0x5
;
     printf
(\"-parityn\" + 
(( flags & 0x1 
) 
!= 0 
));
     printf
(\"-breakn\"  + 
(( flags & 0x2 
) 
!= 0 
));
     printf
(\"-xonn\"    + 
(( flags & 0x4 
) 
!= 0 
));
     printf
(\"-rtsn\"    + 
(( flags & 0x8 
) 
!= 0 
));
     return 0
;
}
  

【解答】printf函數調用任何字符串時,如果給一個字符串加1,打印字符串就會丟失原字符串的第1個字符。語句


printf
(\"a stringn\"
);
printf
(\"a stringn\" + 1
);
  

的輸出結果為:


a string
  string
  

表達式((flags&0x4)!=0)的返回值根據該位是否被設置而定,可能為0或1。如果該位已經設置(「-xonn」+0),該程序就打印-xon;如果已清除(「-xonn」+1),則打印xon。

這裡的輸出結果如下:


parity
-break
xon
-rts
  

由此可見,為了增加程序的易讀性,應添加必要的註釋。

【例17.6】請分析下面程序輸出的不是0xc,而是0x3c的原因。


#include <stdio.h>
int main
()
{
    int x=0xf
;
    x=x<<2
;
    printf
(\"%#xn\"
, x
);
    return 0
;
}
  

【解答】將一個數的每個位全部左移若干位,如x<<2表示將x中各二進位左移2位,如果x值為16進制數0xf,左移2位後,右邊空出來的位補0。在這4位中,確實是c。但寄存器是32位,左移將11移到上一位,打印出來就是0x3c。如果使用x=0xffffffff,左移2位就是0xfffffffc。對0xf而言,要想得到只有4位的值,可以使用「&」運算符,即使用


x=
( x & 0x0f
);
  

語句將11置為00,打印的就是0xc。

【例17.7】請分析下面程序的輸出結果。


#include <stdio.h>
int main
()
{
     int x=0xffffffff
, y=0xf
;
     int a=0225
, b=-60
;
     x=x>>3
;     y=y>>3
;
     printf
(\"x=%#x y=%#xn\"
, x
, y
);
     a=a>>2
;     b=b>>2
;
     printf
(\"a=%#o b=%#dn\"
, a
, b
);
     return 0
;
}
  

【解答】向右移出去的位將丟失,但問題是左邊空出的位如何填充。也就是向右移位時,空出的位是用0填充,還是用1填充。其實,這個問題有時與具體的C語言的實現有關。

如果被移的變量是無符號數,空出的位用0填充,這種補0的方式稱為「邏輯右移」。如果被移的變量是有符號數,那麼C語言實現既可用0填充,也可用1(符號位的副本)填充(這種補1以保持負號的方法稱「算術右移」。如果將變量聲明為無符號類型,則採取「邏輯右移」方式。

這裡使用的系統對負數採取「算術右移」。由此可知x採取補1,移位後不變的方式。y移走3位,左邊補0,為0x1。a是8進制正數,0225代碼為10010101。右移得00100101,即045。b是10進制負數,負數以補碼形式表示,-60的補碼為11000100,右移2位為11110001,即10進制-15。由此可得出程序的輸出結果為:


x=0xffffffff y=0x1
a=045 b=-15
  

【例17.8】移位計數(即移位操作的位數)允許的取值範圍是什麼?

【解答】這要看整數的位數。舉例來說,如果一個int型整數是32位,n是一個int型整數,那麼n<<31和n<<0的寫法都是合法的,而n<<32和n<<-1的寫法則是非法的。

需要注意的是,即使C語言實現將符號位複製到空出的位中,有符號整數的向右移位運算也並不等同於除以2的某次冪。要證明這一點,可以考慮(-1)>>1,這個操作的結果一般不可能為0,但是(-1)/2在大多數C實現上求值結果都是0。這意味著以除法運算來代替移位運算,將可能導致程序運行大大減慢。假設a+b>0,則


c = 
( a + b 
)>>1
;
  

與下式


c = 
( a + b 
) / 2
;
  

完全等效,而且前者的執行速度也要快得多。