---
# System prepended metadata

title: 位元操作
tags: [C]

---

---
title: '位元操作'
disqus: kyleAlien
---

位元操作
===

## OverView of Content

越接近底層（越靠近硬體），我們越常使用到位元運算，所以位元操作必須學習 (順便練練你的邏輯)

位元也就是 Byte (8 個 Bit)，C 語言中大多數的 **位元運算都是透過 `define` 宏去定義**（省呼叫函數的開銷）

:::success
* 如果喜歡讀更好看一點的網頁版本，可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/)
    
    本篇文章對應的是 [**理解C語言中的位元操作：位元運算基礎與宏定義**](https://devtechascendancy.com/bitwise-operations-and-macros-in-c/)
:::

[TOC]


## 運算基礎：位元、邏輯、位移

1. C 語言有提供幾個操作符號來完成位元的運算

    | 符號 | 記憶名稱 | 英文 |
    | -------- | -------- | - |
    | & | 與 | AND |
    | \| | 或 | OR |
    | ^ | 位異 | XOR |
    | ~ | 反向 | NOT |

2. 上面這幾種運算還有在分為 **無符號、有符號** 的位移操作

    | 符號差異 + 位移 | 名稱 |
    | -------- | -------- |
    | 無符號 >> | 右位移 |
    | 無符號 << | 左位移 |
    | 有符號 >> | 右位移 |
    | 有符號 << | 左位移 |


* 這裡寫一個簡單的 `cal.sh` Shell 檔，方便之後在虛擬中斷機上計算結果
    
    ```shell=
    #!/bin/bash

    cal() {
        # 參數 1, 10 進制轉為 2 進制
        x1=$(echo "ibase=10;obase=2;$1" | bc)

        # 參數 3, 10 進制轉為 2 進制
        x2=$(echo "ibase=10;obase=2;$3" | bc)

        case $2 in
            ">>"|"<<")
                x2=$3
            ;;
        esac

        # 以下是顯示、計算步驟
        # 顯示轉換二進位的計算
        echo "1. change to binary:"
        echo "	 $x1 $2 $x2"	

        # 進行計算
        echo "2. cal"
        x3=$(echo "$[$1 $2 $3]")
        # 將結過轉為二進制
        x4=$(echo "ibase=10;obase=2;$x3" | bc)
        echo "	 $x4"

        # 將十進位結果輸出
        echo "3. change to decimal"
        result=$(echo "$x3")
        echo "	 $result"
    }

    cal $1 $2 $3

    exit 0
    ```
    
    :::info
    Shell 的數學運算請參考「[**建構腳本 - 基礎、數學運算**](https://hackmd.io/G0Q9-UMVSeO7wNpMcaUwvw?view#Bash-shell-----%E7%AC%A6%E8%99%9F)」
    :::

### 「與」運算 `&` 

* 首先我們看看 `&` 運算規則，如下 **真值表**
    | & | 0 | 1 |
    | -------- | -------- | -------- |
    | 0 | 0 | 0 |
    | 1 | 0 | 1 |
    
* `&` 就是 `並`，如其名，將每個數轉為 2 進位分開比較，**要兩個數是 1 才是 1**

    1. 題目：`5 & 9`
        ```c=
        # 1. 先轉為 2 進位
        0101 & 1001

        # 2. 每個位元相對應做 & 運算
        0101
        1001
        ------
        0001

        # 轉為 10 進位
        5 & 9 = 1
        ```
        > ![](https://i.imgur.com/5v8ecIt.png)

    2. 題目：`12 & 9`
        ```c=
        # 1. 先轉為 2 進位
        1100 & 1001

        # 2. 每個位元相對應做 & 運算
        1100
        1001
        ------
        1000

        # 轉為 10 進位
        12 & 9 = 8
        ```
        > ![](https://i.imgur.com/1yfwy9B.png)

### 「與邏輯」運算 `&&`

* 邏輯 `&&` 要將要比較的兩個數看作一個整體（不用分開比較），只要 **該數 ++不等於 0 就是 1++（==負數也算是 1==）**，兩個數都要大於 0 就成立

    1. 題目：`1 && 9` 結果為 True
        ```c=
        1 != 0 => true

        9 != 0 => true
        ```
        > ![](https://i.imgur.com/rfoJH8Y.png)


    2. 題目：`1 && -1` 結果為 True
        ```c=
        1 != 0 => true

        -1 != 0 => true
        ```
        > ![](https://i.imgur.com/2wqpd9g.png)


    3. 題目：`1 && 0` 結果為 Flase
        
        ```c=
        1 != 0 => true

        0 != 0 => false
        ```
        > ![](https://i.imgur.com/UQNWn3M.png)

### 「或」運算 `|`

* 首先我們看看 `&` 運算規則，如下 **真值表**
    | \| | 0 | 1 |
    | -------- | -------- | -------- |
    | 0 | 0 | 1 |
    | 1 | 1 | 1 |

* `|` 就是 `或`，如其名，將每個數轉為 2 進位分開比較，**只要有一個數是 1 那就是 1**

    1. 題目：`8 & 9`
        ```c=
        # 1. 先轉為 2 進位
        1000 & 1001

        # 2. 每個位元相對應做 | 運算
        1000
        1001
        ------
        1001

        # 轉為 10 進位
        8 | 9 = 9
        ```
        > ![](https://i.imgur.com/DcRO9Tt.png)

    2. 題目：`12 & 9`
        ```c=
        # 1. 先轉為 2 進位
        1100 & 1001

        # 2. 每個位元相對應做 | 運算
        1100
        1001
        ------
        1000

        # 轉為 10 進位
        12 | 9 = 13
        ```
        > ![](https://i.imgur.com/i4whsGW.png)

### 「或邏輯」 `||`

* 邏輯 `||` 要將要比較的兩個數看作一個整體（不用分開比較），只要 **該數 ++不等於 0 就是 1++（負數也是 1）**，兩個數一個大於 0 就成立

    1. 題目：`12 || 9` 結果為 True
        ```c=
        12 != 0 => true

        9 != 0 => true
        ```
        > ![](https://i.imgur.com/mQtJv8i.png)


    2. 題目：`12 || 0` 結果為 True
        ```c=
        12 != 0 => true

        0 != 0 => false
        ```
        > ![](https://i.imgur.com/JyYcmvU.png)


    3. 題目：`0 && 0` 結果為 Flase
        ```c=
        0 != 0 => false

        0 != 0 => false
        ```
        > ![](https://i.imgur.com/PbKRcGY.png)


### 「取反」運算 `~`

* 簡單來說就是讓二進制位元反轉，像是… 1 轉 0、0 轉 1
    
    ```c=
    ~0 = 1

    ~1 = 0
    ```


    1. 題目：`~1101`
        ```c=
        ~1101 = 0010 // (2)
        ```

    2. 題目：`~1001`
        ```c=
        ~1001 = 0110 // (6)
        ```

### Xor 運算 `^`

* 首先我們看看 `Xor` 運算規則，這種 Xor 運算很常使用在加法器的進位運算，如下 **真值表**
    
    | ^ | 0 | 1 |
    | -------- | -------- | -------- |
    | 0 | 0 | 1 |
    | 1 | 1 | 0 |
    
    > Shell 中使用 Xor 的運算符為為 `^`

* `Xor` 就是 `位異`，如其名，將每個數轉為 2 進位分開比較，**只要有一個數是 1 那就是 1，但若 ++==兩個都是 1 那則會變為 0==++**

    1. 題目：`12 ^ 11`
        ```c=
        # 1. 先轉為 2 進位
        1100 & 1011

        # 2. 每個位元相對應做 ^ 運算
        1100
        1011
        ------
        0111

        # 轉為 10 進位
        12 ^ 11 = 7
        ```
        > ![](https://i.imgur.com/c3ncTZ0.png)

    2. 題目：`6 & 9`
        ```c=
        # 1. 先轉為 2 進位
        0110 & 1001

        # 2. 每個位元相對應做 | 運算
        0110
        1001
        ------
        1111

        # 轉為 10 進位
        6 | 9 = 15
        ```
        > ![](https://i.imgur.com/i4whsGW.png)

### 「左位移」運算 `<<`

* 將計算數字轉為 2 進位，並向左邊位移，並且位移過後補 0 （位移完總不能空著位子吧 ～）

    1. 題目：`6 << 1`
        ```c=
        # 1. 先轉為 2 進位
        0110 << 1

        # 2. 每個位元相對應做 << 位移
        1100

        # 轉為 10 進位
        6 << 1 = 12
        ```
        > ![](https://i.imgur.com/dI8iYnM.png)

    2. 題目：`6 << 9`
        ```c=
        # 1. 先轉為 2 進位
        0110 << 9

        # 2. 每個位元相對應做 << 位移
        110000000000

        # 轉為 10 進位
        6 << 9 = 3072
        ```
        > ![](https://i.imgur.com/KXtlJeo.png)

    :::success
    * **左移 `<<` 特性**：
        
        每進行一次左移，就會是原本數字的「底數倍數」

        > `x << 左位移數` = x * 底數^左位移數^

        我們拿二進制來看，二進制的底數為 `2`，每經過一次左移，就會是原本數字的 2 倍
    
        > `x << n` = x * 2^n^

        > 以下計算 6 的位移
        > 
        > ![](https://i.imgur.com/KCxk6N8.png)
    :::


### 「右位移」運算 `>>` （2 的補數應用）

* 將計算數字轉為 2 進位，並向右邊位移，位移過後仍需要補足，而 右位移 **補足有兩個狀況**，如下表

    | ➡️ 右移捕的數字 | 可運用在 |
    | - | - |
    | 右補 0 | `無符號` or `有符號正數` |
    | 右補 1 | `有符號負數` |

    1. 題目：正數 `6 >> 1`

        ```c=
        # 1. 先轉為 2 進位
        0110 >> 1

        # 2. 每個位元相對應做 >> 位移
        11

        # 轉為 10 進位
        6 >> 1 = 3
        ```
        > ![](https://i.imgur.com/r5mn0Rc.png)

    2. 題目：正數 `15 >> 3`
        ```c=
        # 1. 先轉為 2 進位
        1111 >> 3

        # 2. 每個位元相對應做 >> 位移
        1

        # 轉為 10 進位
        15 >> 3 = 1
        ```
        > 

        :::success
        * 左移 `<<` 特性
            每進行一次左移，就會是原本數字 / 2^n^ 
            > x << n = x / 2^n^

        :::

* 負數（這裡自己手動算）
    * 題目：**負數** `(-15) >> 3`
        
        1. 首先使用 **二的補數計算** 負數的 2 進位
            ```c=
           // 先取 15 的正數 2 進位
            15 => 0000 1111

            // 將其反向 
            ~(0000 1111) => (1111 0000)

            // 將結果 +1 就是 -15 的二進位
            1111 0001
            ```

        2. 負數位移要用 1 來補 （正數就用 0 來補）
            ```c=
            # 1. 先轉為 2 進位
            (1111 0001) >> 3

            # 2. 每個位元相對應做 >> 位移 3 次
            1111 1000
            1111 1100
            1111 1110    # 位移完結果
            ```

        3. 反向 2 的補數取得 10 進位
            ```c=
            # 反向
            ~(1111 1110) => (0000 0001)

            # 結果 減1
            0000 0000

            # 最終結果加上負號
            0
            ```
    * 題目：`(-5) >> 2`
        
        1. 首先使用 **二的補數計算** 負數的 2 進位
            ```c=
           // 先取 15 的正數 2 進位
            5 => 0000 0101

            // 將其反向 
            ~(0000 0101) => (1111 1010)

            // 將結果 +1 就是 -15 的二進位
            1111 1011
            ```

        2. 負數位移要用 1 來補 （正數就用 0 來補）
            ```c=
            # 1. 先轉為 2 進位
            (1111 1011) >> 2

            # 2. 每個位元相對應做 >> 位移 2 次
            1111 1101
            1111 1110    # 位移完結果
            ```

        3. 反向 2 的補數取得 10 進位
            ```c=
            # 反向
            ~(1111 1110) => (0000 0001)

            # 結果 減1
            0000 0000

            # 最終結果加上負號
            0
            ```

## 位元：位操作

位元的位操作，常使用在對 Register 的設置操作上

### 特定位置：清零 `&`

* 要指定某幾個 bit 位清零，最常用的是使用 `&` 運算

    ```shell=
    int a = 0xAAAAAA;

    # 如果希望 8 bit ~ 15 bit 清零
    a &= 0xFF00FF
    
    # 結果
    a: 0xAA00AA
    ```
    
### 特定位置：置為 1 `|`

* 要指定某幾個 bit 位置為 1，最常用的是使用 `|` 運算

    ```shell=
    int a = 0xAAAAAA;

    # 如果希望 8 bit ~ 15 bit 置為 1
    a |= 0x00FF00
    
    # 結果
    a: 0xAAFFAA
    ```
    
### 特定位置 - 反向 `^`

* 要指定某幾個 bit 位反向（與原先的設定相反），最常用的是使用 `^` 運算
    
    ```shell=
    int a = 0xAAAAAA;

    # 如果希望 8 bit ~ 15 bit 反向
    a ^= 0x00FF00
    
    # 結果
    a: 0xAA55AA
    ```
    
### 建構指定二進位

* 通常透過左位移，我們就可以快速建構出我們要的二進位
    1. bit3 ~ bit7 為 1
        ```c=
        # 1. bit3 ~ bit7 所有的 1 就是 0x1F

        # 2. 位移到最低位 （bit3）
        int result = 0x1F << 3;
        ```
    
    2. bit3 ~ bit7 為 1 && bit23 ~ bit25
        ```c=
        # 1. bit3 ~ bit7 所有的 1 就是 0x1F
        #    bit23 ~ bit25 所有的 0x07

        # 2. 位移到最低位 （bit3）
        int result = (0x1F << 3) | (0x07 << 23);
        ```

* 如果位移的 1 過多，不如就使用 `~` 反向
    1. bit4 ~ bit10 為 0 其他都為 1
        ```c=
        # 過多 1 位移
        (0x1fffff << 11) | (0x0f < 0)

        # ----------------------------------------------------
        # 1. bit4 ~ bit10 所有的 1 就是 0x7F

        # 2. ++反向++再位移到最低位 （bit4）
        int result = (~0x7F) << 4;
        ```

### 位元練習

1. bit7 ~ bit17 賦予 654
    ```c=
    // 首先將 bit7 ~ bit17 至為 0
    a = a & ~(0x7FF << 7)

    // 654 位移到最低位 做 or
    a = a | (654 << 7)
    ```

2. 「bit7 ~ bit17 賦予 666」、「bit21 ~ bit25 賦予 99」

    ```c=
    // 首先將 bit7 ~ bit17、bit21 ~ bit25 至為 0
    a = a & ~((0x7FF << 7) | (0x1F << 21))

    // 654 位移到最低位 做 or
    a = a | ((666 << 7) | (99 << 21))
    ```

## 宏定義 - 位元

Linux 內核中有許多宏定義來協助開發者使用，接下來我們拿幾個宏來舉例

:::info
在 `define` 中的運算都需要使用 `()`，避免預編譯時造成運算錯誤
:::

### 宏定義 - 指定位為 1

1. **宏模型**：`#define SET_BIT_N(x, n)  xxx`
    > x: 計算目標
    > n: 第幾位制為 1

2. **宏模型實現**：`#define SET_BIT_N(x, n)  ((x) | ( 1 << (n - 1) ))`
    

### 宏定義 - 指定為復位 0

1. **宏模型**：`#define CLS_BIT_N(x, n)  xxx`
    > x: 計算目標
    > n: 第幾位制為 0

2. **宏模型實現**：`define CLS_BIT_N(x, n)  ((x) & ~( 1 << (n - 1) ))`

### 宏定義 - 擷取指定位元

* 假設我們有一個數為 0x66（轉換為二進制就是`0b01100110`），那這個數的 bit6 ~ bit2 就是 `0b10011`

    1. **宏模型**：`define GET_BITES (x, n, m) xxx`


    2. **宏模型實現**：`define GET_BITES (x, n, m) ((x & ~(\~(0U) << (m-n+1))<<(n+1)) >> (n-1))`
        > x: 計算目標
        > n: 開始位
        > m: 結束位

        * 宏模型的實現步驟分析

            1. **要先從最內部開始分析**：就是 `~(0U) << (m-n+1)`
                > eg. 取 0x66 的 bit6 ~ bit2
                > 就是 GET_BITES(0x66, 2, 6)
                > > x=0x66, n=2, m=6
                > 
                > ~(0U) << (6-2+1)
                > Temp ans1: 1111 1111 1111 1111 1111 1111 1110 0000

            2. **取正確數量的數據**：反向 `~(~(0U) << (m-n+1))`
                > ~(Temp ans1)
                > 
                > Temp ans2: 0000 0000 0000 0000 0000 0000 0001 1111

            3. **移動到正確位置**：左位移 `~(~(0U) << (m-n+1))<<(n+1)`
                > (Temp ans2) << (2+1)
                > 
                > Temp ans3: 0000 0000 0000 0000 0000 0000 1111 1000

            4. **取得數據**：`x & ~(~(0U) << (m-n+1))<<(n+1)`
                > Temp ans4: 0x66 & Temp3
                > Temp ans4: 0x66 & 0xF8
                > Temp ans4: 0x60

            5. **位移回正常位置**
                > Final Ans: Temp ans4 >> (n+1)
                > Final Ans: 0x60 >> (3) = 0b0000 1010
                > Final Ans: 10

## 更多的 C 語言相關文章

關於 C 語言的應用、研究其實涉及的層面也很廣闊，但主要是有關於到系統層面的應用（所以 C 語言又稱之為系統語言），為了避免文章過長導致混淆重點，所以將文章係分成如下章節來幫助讀者更好地從不同的層面去學習 C 語言

### C 語言基礎

* **C 語言基礎**：有關於到 C 語言的「語言基礎、細節」

    :::info
    * [**理解C語言中的位元操作：位元運算基礎與宏定義**](https://devtechascendancy.com/bitwise-operations-and-macros-in-c/)

    * [**C 語言解析：void 意義、NULL 意義 | main 函數調用、函數返回值意義 | 臨時變量的產生**](https://devtechascendancy.com/meaning_void_null_return-value_temp-vars/)

    * [**C 語言中的 Struct 定義、初始化 | 對齊、大小端 | Union、Enum**](https://devtechascendancy.com/c-struct_alignment_endianness_union_enum/)

    * [**C 語言儲存類別、作用域 | 修飾語、生命週期 | 連結屬性**](https://devtechascendancy.com/c-storage-scope-modifiers-lifecycle-linkage/)

    * [**指標 & Array & typedef | 指標應用的關鍵 9 點 | 指標應用、細節**](https://devtechascendancy.com/pointers-arrays-const-typedef-sizeof-null/)

    :::

### 編譯器、系統開念

* **編譯器、系統開念**：是學習完 C 語言的基礎（或是有一定的程度）之後，從編譯器以及系統的角度重新檢視 C 語言的一些細節

    :::warning
    * [**理解電腦記憶體管理 | 深入瞭解記憶體 | C 語言程式與記憶體**](https://devtechascendancy.com/computer-memory_manager-c-explained/)

    * [**C 語言記憶體區塊規劃 | Segment 段 | 字符串特性**](https://devtechascendancy.com/c-memory-segmentation-string-properties/)

    * [**編譯器的角度看程式 | 低階與高階、作業系統、編譯器、直譯器、預處理 | C語言函數探討**](https://devtechascendancy.com/compiler-programming-os-c-functions/)
    :::

### C 語言與系統開發

* **C 語言與系統開發**：在這裡會說明 C 語言的實際應用，以及系統為 C 語言所提供的一些函數、庫... 等等工具，看它們是如何實現、應用

    :::danger
    * [**了解 C 語言函式庫 | 靜態、動態函式庫 | 使用與編譯 | Library 庫知識**](https://devtechascendancy.com/understanding-c-library-static-dynamic/)

    * [**Linux 宏拓展 | offsetof、container_of 宏、鏈表 | 使用與分析**](https://devtechascendancy.com/linux-macro_offsetof_containerof_list/)
    :::
## Appendix & FAQ

:::info
:::

###### tags: `C`
