---
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
```
> 
2. 題目:`12 & 9`
```c=
# 1. 先轉為 2 進位
1100 & 1001
# 2. 每個位元相對應做 & 運算
1100
1001
------
1000
# 轉為 10 進位
12 & 9 = 8
```
> 
### 「與邏輯」運算 `&&`
* 邏輯 `&&` 要將要比較的兩個數看作一個整體(不用分開比較),只要 **該數 ++不等於 0 就是 1++(==負數也算是 1==)**,兩個數都要大於 0 就成立
1. 題目:`1 && 9` 結果為 True
```c=
1 != 0 => true
9 != 0 => true
```
> 
2. 題目:`1 && -1` 結果為 True
```c=
1 != 0 => true
-1 != 0 => true
```
> 
3. 題目:`1 && 0` 結果為 Flase
```c=
1 != 0 => true
0 != 0 => false
```
> 
### 「或」運算 `|`
* 首先我們看看 `&` 運算規則,如下 **真值表**
| \| | 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
```
> 
2. 題目:`12 & 9`
```c=
# 1. 先轉為 2 進位
1100 & 1001
# 2. 每個位元相對應做 | 運算
1100
1001
------
1000
# 轉為 10 進位
12 | 9 = 13
```
> 
### 「或邏輯」 `||`
* 邏輯 `||` 要將要比較的兩個數看作一個整體(不用分開比較),只要 **該數 ++不等於 0 就是 1++(負數也是 1)**,兩個數一個大於 0 就成立
1. 題目:`12 || 9` 結果為 True
```c=
12 != 0 => true
9 != 0 => true
```
> 
2. 題目:`12 || 0` 結果為 True
```c=
12 != 0 => true
0 != 0 => false
```
> 
3. 題目:`0 && 0` 結果為 Flase
```c=
0 != 0 => false
0 != 0 => false
```
> 
### 「取反」運算 `~`
* 簡單來說就是讓二進制位元反轉,像是… 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
```
> 
2. 題目:`6 & 9`
```c=
# 1. 先轉為 2 進位
0110 & 1001
# 2. 每個位元相對應做 | 運算
0110
1001
------
1111
# 轉為 10 進位
6 | 9 = 15
```
> 
### 「左位移」運算 `<<`
* 將計算數字轉為 2 進位,並向左邊位移,並且位移過後補 0 (位移完總不能空著位子吧 ~)
1. 題目:`6 << 1`
```c=
# 1. 先轉為 2 進位
0110 << 1
# 2. 每個位元相對應做 << 位移
1100
# 轉為 10 進位
6 << 1 = 12
```
> 
2. 題目:`6 << 9`
```c=
# 1. 先轉為 2 進位
0110 << 9
# 2. 每個位元相對應做 << 位移
110000000000
# 轉為 10 進位
6 << 9 = 3072
```
> 
:::success
* **左移 `<<` 特性**:
每進行一次左移,就會是原本數字的「底數倍數」
> `x << 左位移數` = x * 底數^左位移數^
我們拿二進制來看,二進制的底數為 `2`,每經過一次左移,就會是原本數字的 2 倍
> `x << n` = x * 2^n^
> 以下計算 6 的位移
>
> 
:::
### 「右位移」運算 `>>` (2 的補數應用)
* 將計算數字轉為 2 進位,並向右邊位移,位移過後仍需要補足,而 右位移 **補足有兩個狀況**,如下表
| ➡️ 右移捕的數字 | 可運用在 |
| - | - |
| 右補 0 | `無符號` or `有符號正數` |
| 右補 1 | `有符號負數` |
1. 題目:正數 `6 >> 1`
```c=
# 1. 先轉為 2 進位
0110 >> 1
# 2. 每個位元相對應做 >> 位移
11
# 轉為 10 進位
6 >> 1 = 3
```
> 
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`