--- tags: linux2022 --- # 2022q1 Homework3 (quiz3) contributed by < `eric88525` > > [第 3 週測驗題](https://hackmd.io/@sysprog/linux2022-quiz3) # Q1 ```c #define GENMASK(h, l) \ (((~0UL) >> (63 - h)) & ((~0UL) >> (l) << (l))) // (((~0UL) >> (LEFT)) & ((~0UL) >> (l) << (RIGHT))) ``` 1. 從最低位元到 `l` 位元需要為 0,可判斷 shift left 應該為 `l` 次,先 shift right 2. ~0UL 為 0XFFFFFFFFFFFFFFFF 且是無號數,右移 63-h 位元可讓 h 到最高位元為0 3. 做 and 運算可留下左右方的 0 , 並保留共通的 1 + 仔細思考後認為 `(~0UL) >> (l) << (l))` 可以寫成 `(~0UL) << (l)` ,原因是 `(~0UL) >> (l) << (l))` 後,結果為最低位元到 `l` 為 0 其餘為 1,跟`(~0UL) << (l))` 的結果一樣的 # Q2 ```c 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){(struct foo *)(v&~3), v & 3}; // return (struct fd){EXP1, v & 3}; } ``` + 向下對齊 4 bytes 規律: + 0~3 對齊到0 + 4~7 對齊到4 + 8~11 對齊到 8 + 可以觀察到只要把最後兩個位元設成0即可對齊,因此要 `&~3` 並轉形成struct 定義的型態 # Q3 ```c #include <stdint.h> uint8_t rev8(uint8_t x) { x = (x >> 4) | (x << 4); x = ((x & 0xCC) >> 2) | ((x & 0x33) << 2); // x = ((x & 0xCC) >> 2) | (EXP2); x = ((x & 0xAA) >> 1) | ((x & 0x55) << 1); // x = ((x & 0xAA) >> 1) | (EXP3); return x; } ``` ``` abcd efgh // x = ((x & 0xCC) >> 2) | ((x & 0x33) << 2) efgh abcd // ((x & 0xCC) >> 2) | ((x & 0x33) << 2) ghef cdab // x = ((x & 0xAA) >> 1) | ((x & 0x55) << 1) hgfe dcba ``` + 透過 & mask 來讓特定位元交換 # Q4 ```c #include <stdio.h> #include <stdarg.h> #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})[++_foreach_i]) #define foreach_ptr(i, ...) \ for (unsigned _foreach_i = \ (((i) = (void *) ((typeof(i)[]){__VA_ARGS__})[0]), 0); \ (i); (i) = (void *) ((typeof(i)[]){__VA_ARGS__, \ NULL})[++_foreach_i], \ _foreach_no_nullval(_foreach_i, i, \ ((const void *[]){__VA_ARGS__}))) int main() { int i; char *p; foreach_int(i, 0, -1, 100, 0, -99) printf("i is %i\n", i); foreach_ptr(p, "Hello", "world") printf("p is %s\n", p); return 0; } ``` + `__VA_ARGS__` 用於將 macro 中的 `...` 展開 + 在 for 迴圈中: + 用於遍歷的變數為 `_foreach_i` , 因此要在每次迴圈結束都+1,也就是 `++_foreach_i` + `_foreach_i` 上限為 `sizeof((int[]){__VA_ARGS__}) / sizeof(int);` ,求得整個 int 陣列的元素數量 + 編譯時在預處理停止,可以看到 macro 會有以下展開,在宣告 `_foreach_i` 時很巧妙的透過 () 和 = 的特性,先讓 i 等於陣列的第 0 元素,接著把 0 指派給 _foreach_i ```c for (unsigned _foreach_i = (((i) = ((int[]){0, -1, 100, 0, -99})[0]), 0); _foreach_i < sizeof((int[]){0, -1, 100, 0, -99}) / sizeof(int); (i) = ((int[]){0, -1, 100, 0, -99, 0})[_foreach_i++]) printf("i is %i\n", i); ``` # Q5 ```c #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; } ``` # Q6 # Q7 ```c= 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 = (v > 0x3) << 1; v >>= m; ret |= m; ret += v > 1; return ret; } ``` 32 bit 為 0x0000 0000 ~ 0xFFFF FFFF , 0~31 bit + binary search 的概念,每次都先檢查 v 是否大於$n/2$的位元數,確認是否在0~n-1 或是 n~最高位元。 n 初始為32,每當檢查完一輪 , n 都會除以2 + line `4-6` `7-9` `10-12` `13-15` 是做相同的事情,先檢查 v 所含位元,是否大於 v 可能有的最高位元數的一半,舉例: + 第4行先檢查 v 有無大於 32bits 的一半,也就是 0xffff ,第7行檢查 v 有無大於 16bits 的一半,也就是 0xff + 每次檢查完如果有大於一半的位元數,用 ret 來紀錄至少含有的位元數: + line 4 `v > 0xFFFF` 可確定最高位元至少會大於 1<<4 bit,也就是 16 bit,用 ret 紀錄下來 # Q8 ```c= void remove_data(tree &t, int d) { tnode **p = &t; while (*p != 0 && (*p)->data != d) { if (d < (*p)->data) p = &(*p)->left; // EXP12; else p = &(*p)->right; // 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 = &(*r)->left; // r = EXP14; q->data = (*r)->data; q = *r; *r = q->right; } delete q; } ``` + 根據二元樹的規則,節點右側會大於當前節點,左邊則是小於。因此在 `line:5` 比較完數值後,要選擇往右或是往左走 + EXP14 為讓 **r 在 d 節點右邊 subtree 中,往走找到最小值 + 假設 d 為15,15 右邊 subtree 的最小數值為18 ```graphviz digraph{ 10->5; 10->15; 15->14; 15->20; 20->18; 20->33; 15[color=blue]; 18[color=red]; } ``` + 把目標替換成 subtree 的最小數值 ```graphviz digraph{ 10->5; 10->18; 18->14; 18->20; 20->33; 18[color=red] } ``` # Q9 ```c /* 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 - 1u) & ~(MAX_ALIGNMENT - 1u)) ``` + 題目要求為 roundup + ROUND_UP_TO_ALIGNMENT_SIZE(1~16) = 16 + ROUND_UP_TO_ALIGNMENT_SIZE(17~32) = 32 + ROUND_UP_TO_ALIGNMENT_SIZE(33~48) = 48 MAX_ALIGNMENT 的二進位為 `0x10000` ,減一後 `0x1111`,可以把不足16的部分強制進位 進位後需要保留 `0x10000` 以後的資訊,因此要 `& ~(MAX_ALIGNMENT - 1u)` ,讓0~3bit 為0,4bit 以上保留 ``` 0x0000100 + 0x0001111 ----------- 0x0010011 & 0x1110000 ------------- 0x0010000 ``` # Q10 ```c= #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))) \ ? (((__x) + ((__d) >> 1)) / (__d)) \ : (((__x) - ((__d) >> 1)) / (__d)); \ }) ``` + line 3-4: + 先用暫存變數存 x 數值,避免在後續展開時被更動,例如輸入 x++,在後續每次被使用時數值都會變動 + line 5: 檢查 signed 或是 unsigned,如果 unsigned 條件會為 true + signed int -1 = -1 + unsigned int -1 = 4294967295 + line 6: + 判斷正負號是否相同 + line 7-8: 這段可視為 (__x / __d) +- 0.5 + 如果 x / divisor 為負,-0.5 來達到四捨五入 + 如果 x / divisor 為正,+0.5 來達到四捨五入