--- tags: linux2022 --- # 2022q1 Homework3 (quiz3) contributed by < `Chao-Shun-Cheng` > ## 測驗 `1` ### 程式碼解釋 ```c=1 #define GENMASK(h, l) \ (((~0UL) >> (LEFT)) & ((~0UL) >> (l) << (RIGHT))) ``` 首先可以觀察到 `~0UL` 是 `0xffffffffffffffff`,另外左半邊只有邏輯右移,因此可以推斷左半邊就是製造出高位的 `bitmask`。舉例來說,假如 `h` 是 60,那就代表高位的 `bitmask` 會是 `0x0fffffffffffffff`,因此可以推測出 `LEFT` 就是 `63 - h`。再來右邊就是要製造出低位的 `bitmask`,假如說 `l` 是 5,那 `bitmask` 就是 `0xfffffffffffffff0`,因此可以推測出 `RIGHT` 就是 `l`。 ::: warning 我認為右移 `l` 是多餘的,不影響結果 ::: ## 測驗 `2` ### 程式碼解釋 ```c=1 struct foo; struct fd { struct foo *foo; unsigned int flags; }; enum { FOO_DEFAULT = 0, FOO_ACTION, FOO_UNLOCK, } FOO_FLAGS; static inline struct fd to_fd(unsigned long v) { return (struct fd){EXP1, v & 3}; } ``` :::info 參考 `jim12312321` 同學對於 data alignment 的解說以及[Stackoverflow](https://stackoverflow.com/questions/19190502/how-do-i-check-a-memory-address-is-32-bit-aligned-in-c) 的解說。其中有一個關鍵的地方 : ***Sequential addresses refer to sequential bytes in memory.*** 還沒看到這句的時候實在不懂 `jim12312321` 同學的意思,直到看到這個才豁然開朗。 ::: 主要就是要將最後兩個 bits 設為 0 ,另外可以觀察到 `EXP1` 要傳入的型態是 `struct foo *`,因此可以推測會用到型態轉換。因此可以得知 `EXP1` 就是 `(struct foo *)(v & ~3)`。 ## 測驗 `3` ### 程式碼解釋 ```c=1 #include <stdint.h> uint8_t rev8(uint8_t x) { x = (x >> 4) | (x << 4); x = ((x & 0xCC) >> 2) | (EXP2); x = ((x & 0xAA) >> 1) | (EXP3); return x; } ``` 首先可以先觀察第 4 行,會把前 4 個與後 4 個 `bit` 進行交換。 ``` x = 11110000 x >> 4 = 00001111 x << 4 = 00000000 (x >> 4) | (x << 4) = 00001111 ``` 因此可以猜測第 5 行就是兩個 `bit` 一組,去進行交換;第 6 行就是一個 `bit` 一組進行交換。另外 `0xCC` 就是 `11001100`,可以猜測出另一邊的 `bitmask` 要是 `00110011`,因此 `EXP2` 就是 `(x & 0x55) << 2`。`0xAA` 則是 `10101010`,可以猜測出另一邊的 `bitmask` 就是 `01010101`,因此 `EXP3` 就是 `(x & 0x33) << 1`。 ## 測驗 `4` ### 程式碼解釋 ```c=1 #include <assert.h> #define _foreach_no_nullval(i, p, arr) \ assert((i) >= sizeof(arr) / sizeof(arr[0]) || (p)) #define foreach_int(i, ...) \ for (unsigned _foreach_i = (((i) = ((int[]){__VA_ARGS__})[0]), 0); \ _foreach_i < sizeof((int[]){__VA_ARGS__}) / sizeof(int); \ (i) = ((int[]){__VA_ARGS__, 0})[EXP4]) #define foreach_ptr(i, ...) \ for (unsigned _foreach_i = \ (((i) = (void *) ((typeof(i)[]){__VA_ARGS__})[0]), 0); \ (i); (i) = (void *) ((typeof(i)[]){__VA_ARGS__, \ NULL})[EXP5], \ _foreach_no_nullval(_foreach_i, i, \ ((const void *[]){__VA_ARGS__}))) ``` :::info 主要參考 `kevinshieh0225` 同學的解說 ::: 不難發現 `foreach_ptr` 與 `foreach_int` 是要把所有參數的內容指定到 `i` 裡面。第 6 行把 `_foreach_i` 當作是 `index`;第 7 行則去判斷有沒有超過參數範圍;最後第 8 行就是把第 `index` 裡的值 assign 到 `i` 並且進行 `index++`,因此可以推斷出 `EXP4` 就是 `_foreach_i++`。同理,`EXP5` 也是 `_foreach_i++`。 ## 測驗 `5` ### 程式碼解釋 ```c=1 #include <limits.h> int divide(int dividend, int divisor) { int signal = 1; unsigned int dvd = dividend; if (dividend < 0) { signal *= -1; dvd = ~dvd + 1; } unsigned int dvs = divisor; if (divisor < 0) { signal *= -1; dvs = ~dvs + 1; } int shift = 0; while (dvd > (EXP6)) shift++; unsigned int res = 0; while (dvd >= dvs) { while (dvd < (dvs << shift)) shift--; res |= (unsigned int) 1 << shift; EXP7; } if (signal == 1 && res >= INT_MAX) return INT_MAX; return res * signal; } ``` 首先可以看到 4 ~ 15 行賄先把除數跟被除數都先轉換成正數,並將結果的正負號存在 `singal` 這個變數當中。 接下來看到第 18 行的 `while` 迴圈,會找出除數往左 `shift` 幾次後會比被除數大,因此可以推測出 `EXP6` 就是 `dvs << shift`。 第 22 行的 `while` 迴圈會去看除數是不是比被除數小,假如比較小才需要繼續計算。最後 23 ~ 26 行則是透過 `shift` 找出商,並減去轉成傷的部分。因此可以推測出 `EXP7` 就是 `dvd - dvs << shift`。 ## 測驗 `7` ### 程式碼解釋 ```c=1 int ilog32(uint32_t v) { int ret = v > 0; int m = (v > 0xFFFFU) << 4; v >>= m; ret |= m; m = (v > 0xFFU) << 3; v >>= m; ret |= m; m = (v > 0xFU) << 2; v >>= m; ret |= m; m = EXP10; v >>= m; ret |= m; EXP11; return ret; } ``` 首先可以看到第 4 行,去判斷從左數來的 16 個 bits 有無 1,如果有就把結果存進去 `ret` 並且繼續看這 16 個 bits ( 對應到第 5 行);第 7 行則是從這 16 個 bits 繼續去看從左數來的 8 個 bits,然後進行一樣的操作。因此可以推測出 `EXP10` 會是 `(v > 0x3U) << 1`。 可以觀察到 `0xFFFFU` 是 16 個 bits;`0xFFU` 是 8 個 bits;`0xFU` 是 4 個 bits;`0x3U` 是 2 個 bits。因此還要討論最後一個 bit,因此可以推測出 `EXP11` 會做出以下程式碼的行為,不過是濃縮成一行 ```c m = (v > 0x1U); v >>= m; ret |= m; ``` 因此可以知道 `EXP11` 就是 `ret |= (v > 0x1U)` ## 測驗 `8` ### 程式碼解釋 ```c=1 void remove_data(tree &t, int d) { tnode **p = &t; while (*p != 0 && (*p)->data != d) { if (d < (*p)->data) EXP12; else EXP13; } tnode *q = *p; if (!q) return; if (!q->left) *p = q->right; else if (!q->right) *p = q->left; else { tnode **r = &q->right; while ((*r)->left) r = EXP14; q->data = (*r)->data; q = *r; *r = q->right; } delete q; } ``` :::info 可以先參考[二叉搜尋樹刪除](https://www.delftstack.com/zh-tw/tutorial/data-structure/binary-search-tree-delete/)所提及的刪除方法,會方便理解二元數的刪除原理。 ::: 首先可以看到第 4 行的 `while` 迴圈,會在這個迴圈找到要刪除的 `node` ,這邊使用指標的指標,可以找到要刪除的 `node` 以及他的 `parent`。可以知道 `EXP12` 是要往左邊走,`EXP13` 是要往右邊走,因此 `EXP12` 就是 `p = &(*p)->left`;`EXP13` 就是 `p = &(*p)->right`。 接下來就是要討論三種刪除的可能性,首先是要刪除的 `node` 沒有左子樹;再來是要刪除的 `node` 沒有右子樹,相對應的操作在 14 ~ 17 行;最後就是有左右子樹,因此只要將右子樹中最小值搬到要刪除的 `node` 位置。可以推測出 20 行的 `while` 迴圈是要找出右子樹中的最小值,因此 `EXP14` 就是 `&(*r)->left`。 ## 測驗 `9` ### 程式碼解釋 ```c=1 /* maximum alignment needed for any type on this platform, rounded up to a power of two */ #define MAX_ALIGNMENT 16 /* Given a size, round up to the next multiple of sizeof(void *) */ #define ROUND_UP_TO_ALIGNMENT_SIZE(x) \ (((x) + MAX_ALIGNMENT - MMM) & ~(NNN)) ``` :::info 主要參考 `kevinshieh0225` 同學的解說 ::: 首先可以看到是要做出 16 的倍數,因此 address 的最後 4 個 bit 要可以為 0,因此可以先推測出 `NNN` 就是 `MAX_ALIGMENT - 1`。另外由於是向上對齊,所以不足 16 倍數的則要補上去,因此可以猜測出 `MMM` 就是 `1`。 ## 測驗 `10` ### 程式碼解釋 ```c=1 #define DIVIDE_ROUND_CLOSEST(x, divisor) \ ({ \ typeof(x) __x = x; \ typeof(divisor) __d = divisor; \ (((typeof(x)) -1) > 0 || ((typeof(divisor)) -1) > 0 || \ (((__x) > 0) == ((__d) > 0))) \ ? ((RRR) / (__d)) \ : ((SSS) / (__d)); \ }) ``` 首先可以看到第 5 行的條件式,主要是用來判斷除數跟被除數是不是均為正數,另外就是利用 `typeof` 去看型態是不是 `unsigned`。假如符合條件就會進行第一個操作 `((RRR) / (__d))`,不符合的話就會進行 `((SSS) / (__d))`。 回顧本題目就是要進行四捨五入的操作,其中可以觀察到第 7 行的 `/` 在 c 語言當中是無條件捨去,因此要作一些對應的操作使其符合四捨五入的原則。 因此可以推測出 `RRR` 就是 `__x + (__d >> 1)`。另外若有負數的話就要進行減,因此 `SSS` 就是 `__x - (__d >> 1)`。