2010年2月12日 星期五

用 C 語言處理常見的旗標位元運算

Standard
正如國中理化課所說,電子產品都是由滿滿『0、1』所構成,大家早就都了解這件事。但現實上,由於要求的功能愈疊愈多,低階的運算早就被眾人所忽略。無論大家如何忙著處理物件的繼承問題,又或者是因時間太多而研究『1+1=2 慢吞吞語言』,低階的運算還是依然沒有消失,反而更為重要。

位元運算不外乎就是『AND、OR、XOR...』等邏輯概念,主要目的是把『1 變 0』、『0 變 1』或有條件維持不變,在程式中比較常看到的地方,通常會在硬體驅動程式(Driver)之中。而在高階應用程式中,也往往因為要旗標條件的判斷,也會看得到其存在,如 Linux 下的檔案權限。

一個更貼切的例子,假設我們現在寫一個滑鼠驅動程式,我們要怎麼設計資料結構,讓應用程式得知用戶(User)按了哪幾個鍵?用戶可能是一次只按『一個鍵』,也可能一次按『左右兩個鍵』,更有可能按『左鍵和中間鍵』,有多種組合存在。

一般初學程式者可能會用 boolean 或 int 這樣寫:
struct _mouse_event {
        int button1;
        int button2;
        int button3;
        ...
};

但明明只是『0、1』,為何要用到 int 資料形態?除了浪費記憶體,運算上也整整慢了好幾個 CPU Clock。所以,多數程式開發者碰到此類問題,會比較偏好以位元為最小單位來寫:
enum {
        MOUSE_LEFT_BUTTON = (1 << 0),
        MOUSE_MIDDLE_BUTTON = (1 << 1),
        MOUSE_RIGHT_BUTTON = (1 << 2)
};

struct _mouse_event {
        int button;
        ...
};

當設定哪幾個鍵被按下,只要用位元運算設定旗標就可以了:
struct _mouse_event mouse;
mouse.button |= MOUSE_LEFT_BUTTON;
mouse.button |= MOUSE_LEFT_BUTTON | MOUSE_RIGHT_BUTTON;

當然,反向設定也是可以:
mouse.button &= ~(MOUSE_MIDDLE_BUTTON);
mouse.button &= ~(MOUSE_MIDDLE_BUTTON | MOUSE_RIGHT_BUTTON);

後記

以前小時候不懂事,又被很多高階 API 保護得很好,常被驅動程式中的位元運算嚇哭,其實,雖然位元運算看起來可以千變萬化,但就那幾種用法最常用而已。在此筆記之,讓大家一起哭。