Overview of Content
本文將深入探討C語言中的位元運算基礎與宏定義。
首先,我們將介紹運算基礎,包括位元、邏輯、以及位移,著重於「與」運算(&
)、「與邏輯」運算(&&
)、「或」運算(|
)、「或邏輯」運算(||
)、「取反」運算(~
)、Xor運算 (^
)、「左位移」運算(<<
)、「右位移」運算(>>
),並探討其在2的補數應用中的意義。
接著,我們將深入位元操作,包括特定位置的清零、置為1、反向,以及建構指定二進位的方法。
最後,透過位元練習,讓讀者能實際應用所學知識。另外,本文還將探討宏定義在位元操作中的應用,包括指定位為 1、指定復位為 0,以及擷取指定位元的技巧。透過本文的閱讀,讀者將深刻理解C語言中位元操作的重要性,並學會如何運用宏定義進行更有效率的位元處理。
運算基礎:位元、邏輯、位移
越接近底層(越靠近硬體),我們越常使用到位元運算,所以位元操作必須學習 (順便練練你的邏輯)
A. C 語言有提供幾個操作符號來完成 位元的運算
符號 | 記憶名稱 | 英文 |
---|---|---|
& | 與 | AND |
| | 或 | OR |
^ | 位異 | XOR |
~ | 反向 | NOT |
B. 上面這幾種運算還有在分為 無符號、有符號 的位移操作
符號差異 + 位移 | 名稱 |
---|---|
無符號 >> | 右位移 |
無符號 << | 左位移 |
有符號 >> | 右位移 |
有符號 << | 左位移 |
● 這裡寫一個簡單的 cal.sh
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
Shell 的數學運算請參考「建構腳本 - 基礎、數學運算」
「與」運算 &
● 首先我們看看 &
運算規則,如下 真值表
& | 0 | 1 |
---|---|---|
0 | 0 | 0 |
1 | 0 | 1 |
● &
就是 並
,如其名,將每個數轉為 2 進位分開比較,要兩個數是 1 才是 1
A. 題目:5 & 9
# 1. 先轉為 2 進位
0101 & 1001
# 2. 每個位元相對應做 & 運算
0101
1001
------
0001
# 轉為 10 進位
5 & 9 = 1
B. 題目:12 & 9
# 1. 先轉為 2 進位
1100 & 1001
# 2. 每個位元相對應做 & 運算
1100
1001
------
1000
# 轉為 10 進位
12 & 9 = 8
「與邏輯」運算 &&
● 邏輯 &&
要將要比較的兩個數看作一個整體(不用分開比較),只要 該數 不等於 0 就是 1(負數也算是 1),兩個數都要大於 0 就成立
A. 題目:1 && 9
結果為 True
1 != 0 => true
9 != 0 => true
B. 題目:1 && -1
結果為 True
1 != 0 => true
-1 != 0 => true
C. 題目:1 && 0
結果為 Flase
1 != 0 => true
0 != 0 => false
「或」運算 |
● 首先我們看看 &
運算規則,如下 真值表
| | 0 | 1 |
---|---|---|
0 | 0 | 1 |
1 | 1 | 1 |
● |
就是 或
,如其名,將每個數轉為 2 進位分開比較,只要有一個數是 1 那就是 1
A. 題目:8 & 9
# 1. 先轉為 2 進位
1000 & 1001
# 2. 每個位元相對應做 | 運算
1000
1001
------
1001
# 轉為 10 進位
8 | 9 = 9
B. 題目:12 & 9
# 1. 先轉為 2 進位
1100 & 1001
# 2. 每個位元相對應做 | 運算
1100
1001
------
1000
# 轉為 10 進位
12 | 9 = 13
「或邏輯」 ||
● 邏輯 ||
要將要比較的兩個數看作一個整體(不用分開比較),只要 該數 不等於 0 就是 1(負數也是 1),兩個數一個大於 0 就成立
A. 題目:12 || 9
結果為 True
12 != 0 => true
9 != 0 => true
B. 題目:12 || 0
結果為 True
12 != 0 => true
0 != 0 => false
C. 題目:0 && 0
結果為 Flase
0 != 0 => false
0 != 0 => false
「取反」運算 ~
● 簡單來說就是讓二進制位元反轉,像是… 1 轉 0、0 轉 1
~0 = 1
~1 = 0
A. 題目:~1101
~1101 = 0010 // (2)
B. 題目:~1001
~1001 = 0110 // (6)
Xor 運算 ^
● 首先我們看看 Xor
運算規則,這種 Xor 運算很常使用在加法器的進位運算,如下 真值表
^ | 0 | 1 |
---|---|---|
0 | 0 | 1 |
1 | 1 | 0 |
Shell 中使用 Xor 的運算符為為
^
● Xor
就是 位異
,如其名,將每個數轉為 2 進位分開比較,只要有一個數是 1 那就是 1,但若 兩個都是 1 那則會變為 0
A. 題目:12 ^ 11
# 1. 先轉為 2 進位
1100 & 1011
# 2. 每個位元相對應做 ^ 運算
1100
1011
------
0111
# 轉為 10 進位
12 ^ 11 = 7
B. 題目:6 & 9
# 1. 先轉為 2 進位
0110 & 1001
# 2. 每個位元相對應做 | 運算
0110
1001
------
1111
# 轉為 10 進位
6 | 9 = 15
「左位移」運算 <<
● 將計算數字轉為 2 進位,並向左邊位移,並且位移過後補 0 (位移完總不能空著位子吧 ~)
A. 題目:6 << 1
# 1. 先轉為 2 進位
0110 << 1
# 2. 每個位元相對應做 << 位移
1100
# 轉為 10 進位
6 << 1 = 12
B. 題目:6 << 9
# 1. 先轉為 2 進位
0110 << 9
# 2. 每個位元相對應做 << 位移
110000000000
# 轉為 10 進位
6 << 9 = 3072
● 左移
<<
特性:每進行一次左移,就會是原本數字的「底數倍數」
x << 左位移數
= x * 底數左位移數我們拿二進制來看,二進制的底數為
2
,每經過一次左移,就會是原本數字的 2 倍
x << n
= x * 2n以下計算 6 的位移
「右位移」運算 >>
(2 的補數應用)
● 將計算數字轉為 2 進位,並向右邊位移,位移過後仍需要補足,而 右位移 補足有兩個狀況,如下表
➡️ 右移捕的數字 | 可運用在 |
---|---|
右補 0 | 無符號 or 有符號正數 |
右補 1 | 有符號負數 |
A. 題目:正數 6 >> 1
# 1. 先轉為 2 進位
0110 >> 1
# 2. 每個位元相對應做 >> 位移
11
# 轉為 10 進位
6 >> 1 = 3
B. 題目:正數 15 >> 3
# 1. 先轉為 2 進位
1111 >> 3
# 2. 每個位元相對應做 >> 位移
1
# 轉為 10 進位
15 >> 3 = 1
● 左移
<<
特性每進行一次左移,就會是原本數字 / 2n
x << n = x / 2n
● 負數(這裡自己手動算)
● 題目:負數 (-15) >> 3
A. 首先使用 二的補數計算 負數的 2 進位
/ 先取 15 的正數 2 進位
15 => 0000 1111
// 將其反向
~(0000 1111) => (1111 0000)
// 將結果 +1 就是 -15 的二進位
1111 0001
B. 負數位移要用 1 來補 (正數就用 0 來補)
# 1. 先轉為 2 進位
(1111 0001) >> 3
# 2. 每個位元相對應做 >> 位移 3 次
1111 1000
1111 1100
1111 1110 # 位移完結果
C. 反向 2 的補數取得 10 進位
# 反向
~(1111 1110) => (0000 0001)
# 結果 減1
0000 0000
# 最終結果加上負號
0
● 題目:(-5) >> 2
A. 首先使用 二的補數計算 負數的 2 進位
/ 先取 15 的正數 2 進位
5 => 0000 0101
// 將其反向
~(0000 0101) => (1111 1010)
// 將結果 +1 就是 -15 的二進位
1111 1011
B. 負數位移要用 1 來補 (正數就用 0 來補)
# 1. 先轉為 2 進位
(1111 1011) >> 2
# 2. 每個位元相對應做 >> 位移 2 次
1111 1101
1111 1110 # 位移完結果
C. 反向 2 的補數取得 10 進位
# 反向
~(1111 1110) => (0000 0001)
# 結果 減1
0000 0000
# 最終結果加上負號
0
位元:位操作
位元的位操作,常使用在對 Register 的設置操作上
特定位置:清零 &
● 要指定某幾個 bit 位清零,最常用的是使用 &
運算
int a = 0xAAAAAA;
# 如果希望 8 bit ~ 15 bit 清零
a &= 0xFF00FF
# 結果
a: 0xAA00AA
特定位置:置為 1 |
● 要指定某幾個 bit 位置為 1,最常用的是使用 |
運算
int a = 0xAAAAAA;
# 如果希望 8 bit ~ 15 bit 置為 1
a |= 0x00FF00
# 結果
a: 0xAAFFAA
特定位置 - 反向 ^
● 要指定某幾個 bit 位反向(與原先的設定相反),最常用的是使用 ^
運算
int a = 0xAAAAAA;
# 如果希望 8 bit ~ 15 bit 反向
a ^= 0x00FF00
# 結果
a: 0xAA55AA
建構指定二進位
● 通常透過左位移,我們就可以快速建構出我們要的二進位
A. bit3 ~ bit7 為 1
# 1. bit3 ~ bit7 所有的 1 就是 0x1F
# 2. 位移到最低位 (bit3)
int result = 0x1F << 3;
B. 「bit3 ~ bit7 為 1」&& 「bit23 ~ bit25」
# 1. bit3 ~ bit7 所有的 1 就是 0x1F
# bit23 ~ bit25 所有的 0x07
# 2. 位移到最低位 (bit3)
int result = (0x1F << 3) | (0x07 << 23);
● 如果位移的 1 過多,不如就使用 ~
反向
A. bit4 ~ bit10 為 0 其他都為 1
# 過多 1 位移
(0x1fffff << 11) | (0x0f < 0)
# ----------------------------------------------------
# 1. bit4 ~ bit10 所有的 1 就是 0x7F
# 2. ++反向++再位移到最低位 (bit4)
int result = (~0x7F) << 4;
位元練習
A. bit7 ~ bit17 賦予 654
// 首先將 bit7 ~ bit17 至為 0
a = a & ~(0x7FF << 7)
// 654 位移到最低位 做 or
a = a | (654 << 7)
B. 「bit7 ~ bit17 賦予 666」、「bit21 ~ bit25 賦予 99」
// 首先將 bit7 ~ bit17、bit21 ~ bit25 至為 0
a = a & ~((0x7FF << 7) | (0x1F << 21))
// 654 位移到最低位 做 or
a = a | ((666 << 7) | (99 << 21))
位元宏定義
位元也就是 Byte (8 個 Bit),C 語言中大多數的 位元運算都是透過 define
宏去定義(省呼叫函數的開銷)
Linux 內核中有許多宏定義來協助開發者使用,接下來我們拿幾個宏來舉例
在
define
中的運算都需要使用()
,避免預編譯時造成運算錯誤
宏定義 - 指定位為 1
A. 宏模型:#define SET_BIT_N(x, n) xxx
x: 計算目標
n: 第幾位制為 1
B. 宏模型實現:#define SET_BIT_N(x, n) ((x) | ( 1 << (n - 1) ))
宏定義 - 指定為復位 0
A. 宏模型:#define CLS_BIT_N(x, n) xxx
x: 計算目標
n: 第幾位制為 0
B. 宏模型實現:define CLS_BIT_N(x, n) ((x) & ~( 1 << (n - 1) ))
宏定義 - 擷取指定位元
● 假設我們有一個數為 0x66(轉換為二進制就是0b01100110
),那這個數的 bit6 ~ bit2 就是 0b10011
A. 宏模型:define GET_BITES (x, n, m) xxx
B. 宏模型實現:define GET_BITES (x, n, m) ((x & ~(\~(0U) << (m-n+1))<<(n+1)) >> (n-1))
x: 計算目標
n: 開始位
m: 結束位
● 宏模型的實現步驟分析
A. 要先從最內部開始分析:就是 ~(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
B. 取正確數量的數據:反向 ~(~(0U) << (m-n+1))
~(Temp ans1)
Temp ans2: 0000 0000 0000 0000 0000 0000 0001 1111
C. 移動到正確位置:左位移 ~(~(0U) << (m-n+1))<<(n+1)
(Temp ans2) << (2+1)
Temp ans3: 0000 0000 0000 0000 0000 0000 1111 1000
D. 取得數據:x & ~(~(0U) << (m-n+1))<<(n+1)
Temp ans4: 0x66 & Temp3
Temp ans4: 0x66 & 0xF8
Temp ans4: 0x60
E. 位移回正常位置
Final Ans: Temp ans4 >> (n+1)
Final Ans: 0x60 >> (3) = 0b0000 1010
Final Ans: 10
更多的 C 語言相關文章
關於 C 語言的應用、研究其實涉及的層面也很廣闊,但主要是有關於到系統層面的應用(所以 C 語言又稱之為系統語言),為了避免文章過長導致混淆重點,所以將文章係分成如下章節來幫助讀者更好地從不同的層面去學習 C 語言
C 語言基礎
● C 語言基礎:有關於到 C 語言的「語言基礎、細節」
編譯器、系統開念
● 編譯器、系統開念:是學習完 C 語言的基礎(或是有一定的程度)之後,從編譯器以及系統的角度重新檢視 C 語言的一些細節
C 語言與系統開發
● C 語言與系統開發:在這裡會說明 C 語言的實際應用,以及系統為 C 語言所提供的一些函數、庫... 等等工具,看它們是如何實現、應用