contributed by < Tcc0403
>
作業需求
測驗題目
在 Linux 核心原始程式碼,include/linux/bitfield.h 提及一個巨集 GENMASK,其作用是依據給定的範圍,產生連續的 bitmask,例如:
GENMASK(6, 4)
產生 011100002GENMASK(39, 21)
產生 0x000000ffffe00000 (64 位元)已知我們使用的微處理器架構為 64 位元,且 unsigned long
為 8 位元組寬度 (符合 LP64 資料模型),以下是可能的 GENMASK
巨集的定義:
請補完,使其行為符合預期。作答規範:
LEFT
和 RIGHT
應以最簡潔的形式撰寫,且符合作業一排版規範 (近似 Linux 核心程式碼排版風格)LEFT
和 RIGHT
皆為表示式,可能包含常數LEFT
和 RIGHT
皆不該包含小括號 (即 (
和 )
)觀察 bitmask 的產生方式為將兩個 ~0UL
(即 64 位元皆為 1)進行相應的位移操作後,做 AND
操作,因此我們可以推斷位移操作的目的是將範圍外的位元變為 0 ,以下計算前後兩項所需清除的位元:
(~0UL) >> (LEFT)
: 該項為右移,目的為清除範圍外的高位元,最高位元是第 63
位元,因此需要清除 63-h
個位元,故 LEFT
= 63-h
(~0UL) >> (l) << (RIGHT)
: 該項為左移,目的為清除範圍外的低位元,最低位元是第 0
位元,因此需要清除 l - 0
= l
個位元,故 RIGHT
= l
其中 (~0UL) >> (l) << (RIGHT)
的部分不太清楚為甚麼需要先右移再左移,範圍外的高位元應該在前一項就已經排除了,這邊的右移操作不知道有甚麼考量
答案 :
LEFT
= 63-h
RIGHT
= l
考慮以下程式碼:
函式 fo_fd 接受指定的地址 v,填入一個 fd 結構體,並確保第一個成員的地址得以依據〈你所不知道的 C 語言:記憶體管理、對齊及硬體特性〉描述,對 4 個位元組進行向下對齊 (即 C++ Boost 描述的 align_down)。其中 struct foo; 運用〈你所不知道的 C 語言:指標篇〉提及的 forward declaration 技巧,可在實作階段才提供具體 struct foo 的定義。
請補完,使其行為符合預期。作答規範:
EXP1
應以最簡潔的 C99 形式撰寫,符合作業一排版規範,且不該觸發編譯器警告訊息EXP1
不得使用 %
(modulo) 運算子v | 1
依題目所要求, v
向下對齊到能被 4 整除的地址上,將 v
的 bit 0 跟 bit 1 清除便是 4 的倍數,使用位元遮罩來保留 bit 0, 1 以外的位元,即 v & ~3
。
接著透過 (struct foo *)
轉型成 foo
結構體的指標存取該地址
答案:
EXP1
= (struct foo *)(v & ~3)
考慮以下程式碼,能將輸入的 8 位元無號整數逐一位元反轉,如同 LeetCode 190. Reverse Bits 的描述。
請補完,使其行為符合預期。作答規範:
EXP2
和 EXP3
應以最簡潔的 C99 形式撰寫,符合作業一排版規範,且不該觸發編譯器警告訊息v | 1
觀察題目,得知該程式碼逐步交換位元,步驟如下:
(x & 0xCC)
為圖中灰色的位元,剩餘的白色位元即為 (x & ~0xCC)
=(x & 0x33)
,對應的位移操作為左移 2 位。 EXP2
= (x & 0x33) << 2
(x & 0xAA)
為圖中灰色的位元,剩餘的白色位元即為 (x & ~0xAA)
=(x & 0x55)
,對應的位移操作為左移 1 位。 EXP3
= (x & 0x55) << 1
答案 :
EXP2
= (x & 0x33) << 2
EXP3
= (x & 0x55) << 1
延伸 〈你所不知道的 C 語言:前置處理器應用篇〉,我們嘗試將 foreach 巨集 予以擴充,使得支援以下寫法:
預期輸出如下:
對應的巨集定義:
請補完,使其行為符合預期。作答規範:
EXP4
和 EXP5
應以最簡潔的 C99 形式撰寫,符合作業一排版規範,且不該觸發編譯器警告訊息題目中 foreach
的功能為走訪第一個除外的所有參數,並將其值指派給第一個參數
在 foreach_int
巨集中,分析 for (clause-1; exp-2; exp-3)
陳述句中的三個部分:
clause-1
: 初始化 i
和 _foreach_i
exp-2
: 判斷 _foreach_i
是否超過可走訪大小exp-3
: 更新 i
和 _foreach_i
我們需要補上更新的部分, i
需要更新為下一個參數, (int[]){__VA_ARGS__, 0}
是將欲走訪之參數視作陣列,透過 array subscript 存取下一個元素,利用 ++_foreach_i
,同時更新 _for_each_i
。故 EXP4
= ++_foreach_i
,__VA_ARGS__
後面補上 0
可以避免因前置遞增運算子而存取到錯誤的記憶體空間
foreach_iptr
巨集中與 foreach_int
類似,走訪各個字串,其中 _foreach_no_nullval
巨集的功能為檢查空字串。 EXP5
的功能與 EXP4
相同,皆是在走訪下一個元素的同時更新作為索引值的變數
答案 :
EXP4
= ++_foreach_i
EXP5
= ++_foreach_i
針對 LeetCode 29. Divide Two Integers,以下是可能的實作:
請補完,使其行為符合預期。作答規範:
EXP6
和 EXP7
應以最簡潔的 C99 形式撰寫,符合作業一排版規範,且不該觸發編譯器警告訊息先觀察 EXP6
所在迴圈的功用,推測為計算除數對齊被除數所需要的最大位移量,當位移後的除數大於被除數便跳出迴圈,故 EXP6
= dvs << shift
再來觀察 EXP7
所在迴圈的功用,推測為計算商數
1
填入商數中相對應的位元EXP7
= dvd -= dvs << shift
EXP6
= dvs << shift
EXP7
= dvd -= dvs << shift
針對 LeetCode 149. Max Points on a Line,以下是可能的實作:
請補完,使其行為符合預期。作答規範:
EXP8
和 EXP9
應以最簡潔的 C99 形式撰寫,符合作業一排版規範,且不該觸發編譯器警告訊息EXP8
不該出現小括號 (即 (
和 )
)EXP9
為包含 list_
開頭巨集的單一敘述根據 EXP8
所在的函式名稱,猜測該函式的功能為判斷座標 (p1, p2) 是否符合某種條件,可以插入該位置的鏈結串列,觀察尋找呼叫該函式的程式碼段落,判斷此條件為
考慮 ilog32
函式可針對給定的 32 位元無號數,計算扣除開頭的 0,最少需要多少位元才可儲存 (the minimum number of bits required to store an unsigned 32-bit value without any leading zero bits),以下是一個可能的實作:
請補完,使其行為符合預期。作答規範:
EXP10
和 EXP11
應以最簡潔的 C99 形式撰寫,符合作業一排版規範,且不該觸發編譯器警告訊息EXP11
不該出現小括號 (即 ( 和 ))EXP10
和 EXP11
皆包含 > 運算子考慮以下 C++ 二元搜尋樹的程式:
函式 remove_data
嘗試將指定的資料,自給定的二元搜尋樹移除,但原本的 remove_data
過於冗長,於是我們可善用 indirect pointer 改寫為以下:
請補完,使其行為符合預期。作答規範:
EXP12
, EXP13
和 EXP14
應以最簡潔的 C99 形式撰寫,符合作業一排版規範,且不該觸發編譯器警告訊息,並善用 indirect pointer 的技巧來撰寫
EXP12
, EXP13
和 EXP14
皆不該出現 ;
考慮以下進行記憶體地址對齊 (alignment) 的巨集:
作用是向上對齊 (roundup),請補完程式碼,使其行為符合預期。作答規範:
MMM
是常數NNN
是表示式MMM
和 NNN
皆不得出現小括號 (即 ( 和 ))考慮以下巨集可得到二個表示式進行整數除法操作時,最接近的整數值:
注意: 當除數 (即 divisor) 為負值時,行為沒有定義。
參考執行結果:
DIVIDE_ROUND_CLOSEST(7, 3) = 2
DIVIDE_ROUND_CLOSEST(6, 3) = 2
DIVIDE_ROUND_CLOSEST(5, 3) = 2
作答規範:
RRR
和 SSS
為表示式,且都包含 (__x)
和 (__d)
(注意小括號)RRR
和 SSS
限用 +
, -
, >>
, <<
這幾個運算子,可搭配小括號,並以符合 C99 的最精簡形式撰寫RRR
和 SSS
出現的順序 (從左到右),必定是 __x
先於 __d
考慮以下計算整數開平方根的實作:
i_sqrt
函式的作用等同於 floor(sqrt(x))
作答規範:
XXX
, YYY
和 ZZZ
都是敘述,不得呼叫任何函式,且不包含 ; 字元=
, +=
, -=
, >>=
, <<=
等運算子,且不得出現小括號 (即 ( 和 ))