理解C語言中的位元操作:位元運算基礎與宏定義

理解C語言中的位元操作:位元運算基礎與宏定義

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 的數學運算請參考「建構腳本 - 基礎、數學運算

「與」運算 &

● 首先我們看看 & 運算規則,如下 真值表

&01
000
101

& 就是 ,如其名,將每個數轉為 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

「或」運算 |

● 首先我們看看 & 運算規則,如下 真值表

|01
001
111

| 就是 ,如其名,將每個數轉為 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 運算很常使用在加法器的進位運算,如下 真值表

^01
001
110

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 語言所提供的一些函數、庫... 等等工具,看它們是如何實現、應用

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

發表迴響