contributed by < Kevin-Shih
>
題目
在 Linux 核心原始程式碼,include/linux/bitfield.h 提及一個巨集 GENMASK
,其作用是依據給定的範圍,產生連續的 bitmask,例如:
GENMASK(6, 4)
產生 011100002GENMASK(39, 21)
產生 0x000000ffffe00000 (64 位元)已知我們使用的微處理器架構為 64 位元,且 unsigned long
為 8 位元組寬度 (符合 LP64 資料模型),以下是可能的 GENMASK
巨集的定義:
請補完,使其行為符合預期。
AND
的左邊可以看出是要透過 ~0UL
右移產生低位的 h
bits 均為 1 的 unsigned long
,而右側則是以左移產生低位 l
bits 均為 0 的 unsigned long
接著就可以用 AND
運算得到 h
至 l
之間的 bits 均為 1 的 bitmask。
因此 LEFT
應為 63 - h
,以產生低位 h
bits 均為 1 的 unsigned long
, RIGHT
應為 l
以產生低位 l
bits 均為 0 的 unsigned long
。
由於左移超過變數長度時,其結果在 c 屬於未定義行為,因此先左移
l
bits 再右移l
bits 以避免此情形發生。
TODO
GENMASK
巨集的實作,闡述其額外的考量GENMASK
巨集和 include/linux/bitfield.h 的應用案例GENMASK
巨集的實作,闡述其額外的考量GENMASK
巨集和 include/linux/bitfield.h 的應用案例題目
考慮以下程式碼:
函式 fo_fd
接受指定的地址 v
,填入一個 fd
結構體,並確保第一個成員的地址得以依據〈你所不知道的 C 語言:記憶體管理、對齊及硬體特性〉描述,對 4 個位元組進行向下對齊 (即 C++ Boost 描述的 align_down)。其中 struct foo;
運用〈你所不知道的 C 語言:指標篇〉提及的 forward declaration 技巧,可在實作階段才提供具體 struct foo
的定義。
請補完,使其行為符合預期。
由於 fd
結構體的第一個成員是指向 struct foo
結構體的指標,因此 EXP1
肯定要將 unsigned long
型態的 v
casting 成指向 struct foo
型態的指標,即 EXP1
前面一定包含 (struct foo *)
。
接著由於要作 4 byte 的 align down 因此代表 21, 20 的最低位 2 bits 應設為 0 (低位的 log2(4) 個 bits 設為 0),而這可以透過 AND
一個最低 2 位為 0 的 bitmask 達成,最簡潔的寫法是透過取 3
的 1 補數達成。
因此 EXP1
= (struct foo *)(v & ~3)
TODO
題目
考慮以下程式碼,能將輸入的 8 位元無號整數逐一位元反轉,如同 LeetCode 190. Reverse Bits 的描述。
請補完,使其行為符合預期。
進行 rev8
前的一個 uint8_t
示意圖
為求方便辨識原本的 0 到 7 bit 分別以 0 到 7 標注
#2 將高位 4 bits 與低位 4 bits 對調後
#3 左半邊可以看出是將 4 bits 內的高位 2 bits 調至低位 2 bits,因此不難猜出 EXP2
是將 4 bits 內的低位 2 bits 調至高位 2 bits,調換後的示意圖如下
#4 的左半邊當然就是將 2 bits 內的高位 bit 調至低位 bit,因此 EXP3
是將 2 bits 內的低位 bit 調至高位 bit,調換後的示意圖如下
由上面的步驟得出答案為
EXP2
= (x & 0x33) << 2
EXP3
= (x & 0x55) << 1
0x33
=0b00110011
,0x55
=0b01010101
TODO
題目
延伸〈你所不知道的 C 語言:前置處理器應用篇〉,我們嘗試將 foreach 巨集 予以擴充,使得支援以下寫法:
預期輸出如下:
對應的巨集定義:
請補完,使其行為符合預期。
從程式碼可以看出不論是 foreach_int
, foreach_ptr
中的 _foreach_i
都是用來標示讀到第幾個 __VA_ARGS__
的,因此每次迴圈 foreach_int
應加 1,而由於每次迴圈的更新要對 i
賦值,使其值等於下個 __VA_ARGS__
中的值,因此 EXP4
, EXP5
= ++_foreach_i
,而非 _foreach_i++
。 (先更新 foreach_int
再對 i
賦值)
TODO
題目
針對 LeetCode 29. Divide Two Integers,以下是可能的實作:
請補完,使其行為符合預期。
#5 ~ #15 用於處理當除數或被除數有負值的時候將其轉為正值後存入對應的 unsigned int
,並將其正負號紀錄於 signal
變數,方便後續運算。
#18 則是當被除數大於除數乘上 2shift 時,代表商的最高位的 1 (first set bit) 至少在第 shift
bit。 就像十進位除法 900 除 10 我們會移到百位數後發現 9 < 10 最後從十位數開始寫商,而更大的位數則留下 0。
實際上跳出迴圈時
shift
會比商的 first set bit 的 index 多 1 但會在後續的 while loop handle
因此 EXP6
= dvs << shift
#22 開始的 while loop 是整個除法的主體,內層的 while loop 就是在處理先前比喻的 9 < 10 的情形下商會退 1 位再繼續寫若退 1 位後還是被除數還是小於位移後的除數就退到不小於為止。 當被除數不小於位移後的除數時就將 1 紀錄到 res
中對應的 bit,並將被除數減去位移後的除數的值,因此 EXP7
= dvd -= dvs << shift
,而 while loop 會進行到剩下的 dvd
小於 dvs
為止。
最後將 res
乘上先前紀錄的正負號 signal
即為答案。
TODO
題目
針對 LeetCode 149. Max Points on a Line,以下是可能的實作:
請補完,使其行為符合預期。
TODO
題目
考慮 ilog32
函式可針對給定的 32 位元無號數,計算扣除開頭的 0
,最少需要多少位元才可儲存 (the minimum number of bits required to store an unsigned 32-bit value without any leading zero bits),以下是一個可能的實作:
請補完,使其行為符合預期。
#3 當 v
是非零的值就至少需要 1 bit 紀錄,故 ret = v > 0
#4 ~ #6 當 v
的 first set 在高位的 16 bits 中, v
會右移 16 bits 並在剩下的 16 bits 繼續找 first set , ret
則加上 16。 若不是的話, m
會為 0 不影響後續步驟。 (會變成找低位的 16 bits 中使否有 first set)
#7 ~ #9 則是找在剩下的 16 bits 中 v
的 first set 是否在較高的 8 bits 中,若有 v
會再右移 8 bits 並在剩下的 8 bits 繼續找 first set , ret
則加上 8。
後面依此類推,EXP10
是在最後 4 bits 中找 v
的 first set 是否在較高的 2 bits 中,因此 EXP10
= (v > 3) << 1
。 EXP11
是在最後 2 bits 中找 v
的 first set 是在較高的 bit 中或是較低的那個,若是在較高的就將 ret += 1
若不是就不用加,因此 EXP11
= ret += v > 1
。
EXP11
已經是最後一步了,所以直接改ret
就好,不須再對v
做位移或先紀錄到m
。
TODO
ilog
ilog2
實作ilog
ilog2
實作題目
考慮以下 C++ 二元搜尋樹的程式:
函式 remove_data
嘗試將指定的資料,自給定的二元搜尋樹移除,但原本的 remove_data
過於冗長,於是我們可善用 indirect pointer 改寫為以下:
請補完,使其行為符合預期。
前面的 while loop 是在找要刪除的 node 依據二元搜尋樹的特性當要刪除的 node d
小於當前的 node 的 data,應向 left child 繼續搜尋,大於時則向 right child 搜尋。 因此 EXP12
= p = &(*p)->left
, EXP13
= p = &(*p)->right
。
將 indirect pointer
p
指向*p
的 left 或 right 所在的記憶體位置
當要移除的 node 存在時 *p
會指向該 node,若否則 *p
會等於 null
,而 p
則是指向該 node 的 parent 的 left 或 right 所在的記憶體位置。 因此接下來只須將 *p
指向接替 node d
的 node 就完成 remove 了。
沒有左子樹或右子樹時
當被移除的 node 沒有左子樹時直接以右子樹取代該 node。 反之若沒有右子樹時直接以左子樹取代該 node。
左右子樹都存在時
這邊是從右子樹中找最小的 node 取代要移除的 node。 先以 r
指向要移除的 node 的 right,當該 node 有左子葉時 r
就指向 (*r)->left
所在的記憶體位置,一路找到沒有左子葉時就是右子樹中最小的 node。 接著將這個 node 的 data 複製到要刪除的 node 中取代要被刪除的值,並將 q
指向該 node,並將 *r
指向 q
的右子樹。 因此 EXP14
= &(*r)->left
。
最後刪除 q
即可。
TODO