工程師應知道的0x10個問題(9): Bit操作

MuLong PuYang
7 min readMar 5, 2022

--

英文參考網址

A ‘C’ Test: The 0x10 Best Questions for Would-be Embedded Programmers

中文參考網址

C語言測試 應知道的0x10個基本問題

原文翻譯

9. Embedded systems always require the user to manipulate bits in registers or variables. Given an integer variable a, write two code fragments. The first should set bit 3 of a. The second should clear bit 3 of a. In both cases, the remaining bits should be unmodified.

嵌入式系統總是需要使用者去操作在暫存器或者變數中的位元。給一個整數變數 a ,寫出兩個程式片段。第一個要去設定 a 的 bit 3。第二個要去清除 a 的bit 3。在這兩個案例中,其他的位元不應該被改動到

These are the three basic responses to this question:

這些是對這個問題的基礎回答

(a) No idea. The interviewee cannot have done any embedded systems work.

(a) 不知道。這個應試者沒有做過做嵌入式系統的工作。

(b) Use bit fields. Bit fields are right up there with trigraphs as the most brain-dead portion of C. Bit fields are inherently non-portable across compilers, and as such guarantee that your code is not reusable. I recently had the misfortune to look at a driver written by Infineon for one of their more complex communications chip. It used bit fields, and was completely useless because my compiler implemented the bit fields the other way around. The moral — never let a non-embedded person anywhere near a real piece of hardware! [3]

(b) 使用位元欄。位元欄就是那種像是三字符組那種在C語言中已經腦死的部分。位元欄是那種本質上在不同編譯器之間是不可移植的,因此也保證了你的程式碼是無法重複使用的。我最近不幸的看到一個英飛凌為其較複雜的通訊晶片寫的驅動程式。它使用位元欄,完全無作用因為我的編譯器使用其他方法實作位元欄。道德上來說,千萬不要讓一個沒有嵌入式經驗的人接近真正的硬體部分。

注1: 三字符組的wiki page: 三字符組與雙字符組
注2: 原中的[3]的原意如下
[3] I’ve recently softened my stance on bit fields. At least one compiler vendor (IAR) now offers a compiler switch for specifying the bit field ordering. Furthermore the compiler generates optimal code with bit field defined registers – and as such I now do use bit fields in IAR applications.

(c) Use #defines and bit masks. This is a highly portable method, and is the one that should be used. My optimal solution to this problem would be:

使用 #defines與bit masks。這是個高度可移植的方法,而且也是個該使用的方法。我對這題的最佳解是:

#define BIT3 (0x1 << 3)

static int a;

void set_bit3(void) {

a |= BIT3;

}

void clear_bit3(void) {

a &= ~BIT3;

}

Some people prefer to define a mask, together with manifest constants for the set & clear values. This is also acceptable. The important elements that I’m looking for are the use of manifest constants, together with the |= and &= ~ constructs.

有些人喜歡定義mask,與明白的常數對set & clear數值。這也是可接受的。重點是我要看到明白的常數與使用 |= 以及 &= ~結構。

自我實作以及理解

注1: 以下是我的自我實作以及理解的部分,不保證正確而且很有可能描述不清楚或者是有錯誤,讀者若發現有可以更正的地方也歡迎留言告訴我注2: 以下皆簡單的使用Ubuntu 20.04虛擬機做測試,並非真實的嵌入式系統,所以Ubuntu 20.04出來的成果可能會與嵌入式系統上的有差異注2: 以下皆簡單的使用Ubuntu 20.04虛擬機做測試,並非真實的嵌入式系統,所以Ubuntu 20.04出來的成果可能會與嵌入式系統上的有差異

位元操作是嵌入式系統很基本的操作也是面試的常問考題之一

基本上我對位元操作的理解,大都來自以下這篇stack overflow的文章,我也會主要使用這篇文章做講解

How do you set, clear, and toggle a single bit?

(一) Set Bit

假定我們現在有一個變數 a 為 0,我們要設定第3的位元為1,那這個時候,我們就讓它 |= (1 << 3),也就是說去 |= 1向左位移 3 個位元的數,也就是說 a 這個數值要去 | 100這個數,就是下方這個結果

000 | 100 = 100 

當然由於是 |= ,這個數值就會賦與給 a 本身

當我們查看編譯結果的時候,就會發現輸出為 8

8

總而言之,就是以下的程式碼(n為設定位元)

a |= (1 << n)

(二) Clear Bit

那假設我們現在設定了第三個位元,如果現在我們想要清掉第三個位元的話,我們該如何實作,這裡int是32位元,這裡我們只呈現一開始的8位元,來簡化說明,a 現在的二進位是00000100,我們如果讓一個 1的數值,同樣也向左位移 3 位元,也就是 00000100,然後這時候反轉,則數值會變為11111011,這時候再 & 起來,則就可以清除掉第 3 個位元

注: a為32位元,這裡簡化為顯示一開始的8位元來簡化解說
00000100 & 11111011 = 0

當然由於是 &= ,這個數值就會賦與給 a 本身

所以我們可以看到輸出為 0

Set bit 3: 8
Clear bit 3: 0

總而言之,就是以下的程式碼(n為設定位元)

a &= ~(1 << n)

(三) Toggle Bit

那如果我們要一次set bit,一次clear bit,那麼我們可以使用toggle bit的方式。我們可以使用 xor 運算子。大家可以參考維基百科的 xor

T為1,F為0,基本上就是相同為0,相異為1

當我們數值為0的時候,這時候我們第一次toggle,這時候就會變成,記住相同為0,相異為1,所以這時候數值就會變為 8

000 ^ 100 = 100

那當我們再toggle一次,記住相同為0,相異為1,這時候就又會變成

100 ^ 100 = 000

當然以上的數值都是 ^= ,這個數值就會賦與給 a 本身

輸出結果

Set bit 3: 8
Clear bit 3: 0
Toggle bit 3(first time): 8
Toggle bit 3(second time): 0

總而言之,就是以下的程式碼(n為設定位元)

a ^= (1 << n)

--

--