# 2022q1 Homework3 (quiz3) contribute by < `Korin777` > ## 測驗一 在 Linux 核心原始程式碼,[include/linux/bitfield.h](https://github.com/torvalds/linux/blob/master/include/linux/bitfield.h) 提及一個巨集 `GENMASK`,其作用是依據給定的範圍,產生連續的 [bitmask](https://en.wikipedia.org/wiki/Mask_(computing)),例如: * `GENMASK(6, 4)` 產生 $01110000_{2}$ * `GENMASK(39, 21)` 產生 0x000000ffffe00000 (64 位元) 已知我們使用的微處理器架構為 64 位元,且 `unsigned long` 為 8 位元組寬度 (符合 [LP64](https://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models) 資料模型),以下是可能的 `GENMASK` 巨集的定義: ```c #define GENMASK(h, l) \ (((~0UL) >> (63 - h)) & ((~0UL) >> (l) << (l))) ``` * `~0UL` 每個位元皆為 1,而 `GENMASK` 則會將 h ~ l 以外的位元都變為 0 * 先將一個 `~0UL` 的 63 ~ h 位元(不包括 h)變為 0 => `(~0UL) >> (63 - h)` * 再將另一個 `~0UL` 的 l ~ 0 位元(不包括 l)變為 0 => `(~0UL) >> (l) << (l)` * 對前兩數做 & 運算即可獲得題目所求 => `((~0UL) >> (63 - h)) & ((~0UL) >> (l) << (l))` ## 測驗二 ```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}; } ``` * [align_down](https://www.boost.org/doc/libs/1_78_0/doc/html/align/reference.html#align.reference.functions.align_down) : A value at or before value that is a multiple of alignment. * 題目要求第一個成員的地址對 4 個位元組進行向下對齊 , 所以該地址必為 4 的倍數且比 v 小 , 即 v 的最後兩個 bit 應為 0 * `~3` 二進位表示中 , 第 0 及第 1 個 bit 為 0 其餘皆為 1 , 因此 `v & ~3` 能夠確保地址為 4 的倍數且比 v 小 ## 測驗三 ```c #include <stdint.h> uint8_t rev8(uint8_t x) { x = (x >> 4) | (x << 4); x = ((x & 0xCC) >> 2) | ((x & 0x33) << 2); x = ((x & 0xAA) >> 1) | ((x & 0x55) << 1); return x; } ``` * `0xCC = 11001100` `0x33 = 00110011` * `0xAA = 10101010` `0x55 = 01010101` 1. 先將 `x` 分為兩半 , 左半邊的 bit 與右半邊互換 , 此時 `x` 被分為兩塊 2. 將分出來的兩塊在各別切為兩半 , 左半邊的 bit 與右半邊互換 , `x` 被分為四塊 `0xCC = 11001100` `0x33 = 00110011` 3. 將分出來的四塊在各別切為兩半 , 左半邊的 bit 與右半邊互換 , `x` 被分為八塊 , 而各塊皆為 1 bit , 反轉完成 e.g : 11010010 1101 | 0010 => 00101101 00|10 11|01 => 10000111 1|0 0|0 0|1 1|1 => 01001011 ## 測驗四 ```c #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__}))) ``` * `foreach_int` 即 `foreach_ptr` 為 [Variadic Macros](https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html) 可以傳入可變數量的參數(即 `...` 可為零或多個) , 而在最後被命名的 argument(即 `i`) 後的所有 argument(包含他們之間的逗號) 會被前處理器展開到有 `__VA_ARGS__` 的地方 * `(((int[]){__VA_ARGS__})[0])` 即展開為 `(((int[]){0, -1, 100, 0, -99})[0])` 也就是 array `((int[]){0, -1, 100, 0, -99})` index 0 的值 * `_foreach_i` 會被 assign 成 `(((i) = ((int[]){__VA_ARGS__})[0]), 0)` 括號中最後的那個值 , 也就是 0 * for 迴圈每跑完一次 `i` 就要改為 array 下一個 index 的值 , 因此 `_foreach_i` 要先加 1(即 `++_foreach_i`) ## 測驗五 ```c #include <limits.h> // dividend / divisor int divide(int dividend, int divisor) { int signal = 1; // 商的正負 // 確保 dvd dvs 為正數 dvd / dvs unsigned int dvd = dividend; if (dividend < 0) { // 被除數為負數 signal *= -1; dvd = ~dvd + 1; // dvd = -dvd } unsigned int dvs = divisor; if (divisor < 0) { // 除數為負數 signal *= -1; dvs = ~dvs + 1; // dvs = -dvs } int shift = 0; while (dvd > (dvs << shift)) // 找出 divisor 乘以 2 的多少次方後會大於 dividend shift++; unsigned int res = 0; while (dvd >= dvs) { // 找出 dividend 除以 divisor 的商之二進位表示中 bit 為 1 的位元 , 並將 res 對應的位元設成 1 while (dvd < (dvs << shift)) shift--; res |= (unsigned int) 1 << shift; dvd -= dvs << shift; } if (signal == 1 && res >= INT_MAX) // 避免 -2147483648 / -1 = 2147483648 => overflow return INT_MAX; return res * signal; } ``` * 找出 `dividend` 除以 `divisor` 的商之二進位表示中 bit 為 1 的位元 , 並將 `res` 對應的位元設成 1 e.g : $101 \div 5 = 20 = 5 \times 2 ^ 4 + 5 * 2 ^ 2 = 10100_{2}$ = `res` ## 測驗六 ```c static bool can_insert(struct list_head *head, int p1, int p2) { struct point_node *pn; list_for_each_entry (pn, head, link) return p1 == pn->p1; return true; } static int maxPoints(struct Point *points, int pointsSize) { if (pointsSize <= 2) return pointsSize; int i, j, slope_size = pointsSize * pointsSize / 2 + 133; int *dup_cnts = malloc(pointsSize * sizeof(int)); int *hori_cnts = malloc(pointsSize * sizeof(int)); int *vert_cnts = malloc(pointsSize * sizeof(int)); int *slope_cnts = malloc(slope_size * sizeof(int)); memset(hori_cnts, 0, pointsSize * sizeof(int)); memset(vert_cnts, 0, pointsSize * sizeof(int)); memset(slope_cnts, 0, slope_size * sizeof(int)); for (i = 0; i < pointsSize; i++) dup_cnts[i] = 1; struct list_head *heads = malloc(slope_size * sizeof(*heads)); for (i = 0; i < slope_size; i++) INIT_LIST_HEAD(&heads[i]); for (i = 0; i < pointsSize; i++) { for (j = i + 1; j < pointsSize; j++) { if (points[i].x == points[j].x) hori_cnts[i]++, hori_cnts[j]++; if (points[i].y == points[j].y) vert_cnts[i]++, vert_cnts[j]++; if (points[i].x == points[j].x && points[i].y == points[j].y) dup_cnts[i]++, dup_cnts[j]++; if (points[i].x != points[j].x && points[i].y != points[j].y) { int dx = points[j].x - points[i].x; int dy = points[j].y - points[i].y; int tmp = gcd(dx, dy); dx /= tmp; dy /= tmp; int hash = dx * dy - 1333 * (dx - dy); if (hash < 0) hash = -hash; hash %= slope_size; if (can_insert(&heads[hash], i, j)) { struct point_node *pn = malloc(sizeof(*pn)); pn->p1 = i; pn->p2 = j; list_add(&pn->link, &heads[hash]); } } } } for (i = 0; i < slope_size; i++) { int index = -1; struct point_node *pn; list_for_each_entry (pn, &heads[i], link) { index = pn->p1; slope_cnts[i]++; } if (index >= 0) slope_cnts[i] += dup_cnts[index]; } int max_num = 0; for (i = 0; i < pointsSize; i++) { if (hori_cnts[i] + 1 > max_num) max_num = hori_cnts[i] + 1; if (vert_cnts[i] + 1 > max_num) max_num = vert_cnts[i] + 1; } for (i = 0; i < slope_size; i++) { if (slope_cnts[i] > max_num) max_num = slope_cnts[i]; } return max_num; } ``` * `hori_cnts[i]` : 用來記錄與點 i 在同一個水平線上的點數量 * `vert_cnts[i]` : 用來記錄與點 i 在同一個鉛直線上的點數量 * `dup_cnts[i]` : 用來記錄與點 i 重複的點數量 * `slope_cnts[i]` : 用來記錄與點 i 在同個斜率 m 之線上的的點數量 * `heads[i]` : 用來連結某個斜率下的所有點 `point_node` * `static bool can_insert(struct list_head *head, int p1, int p2)` : 用來判斷一個點該不該被加到 `list_head[i]` * 每個 `list_head[i]` 一開始都可以插入點 * 當有一點插入 `list_head[i]` 後 , 除非 `p1` 為第一個插入的點 , 否則就不允許插入 , 這是因為 `maxPoints` 會先將 `points[1~pointsSize-1]` 中與 `points[0]` 斜率為 m 的點加入對應的 `list_head[i]` , 再來換看 `points[1]` , 而 `points[1]` 與 `points[2~pointsSize-1]` 一樣有斜率為 m 的點且對應到同一個 `list_head[i]` , 這時要是允許插入 , 就會出現點重複的情況 * maxPoints : 求出 `hori_cnts`、`vert_cnts`、`slope_cnts` , 當中最大的即為 Max Points on a Line 註: 1.`int hash = dx * dy - 1333 * (dx + dy)` 應改為 `int hash = dx * dy - 1333 * (dx - dy);` , 否則 (0,0)、(3,4)、(4,3) 會被判斷再同意條線上 , 因 (0,0)、(3,4) 及 (0,0)、(4,3) `hash` 值相同 2.同樣斜率的多條不同的線只有第一條會被記錄 3.題目的點都是唯一的 , 所以可以不用計算 `dup_cnts` ## 測驗七 ```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 > 3) << 1; v >>= m; ret |= m; ret += v > 1; return ret; } ``` * 題目要求最少需要多少位元才可儲存 , 即 32 位元中最高位元的 1 出現在哪一個 bit * `ret` : 最少需要多少位元才可儲存 1. 每次將 `v` 尚未檢查的 bits 分為兩半 , 並看左半邊是否有值 2. 有的話表示最高位元的 1 在左半邊 , 且我們需要右半邊的 bit 來儲存 `v` , 這時右半邊查看完畢 ; 沒有則表示最高位元的 1 在右半邊 , 這時左半邊查看完畢 , 重複步驟 1 直到剩兩個尚未檢查的 bit 3. 最後兩個尚未檢查的 bit 會分別在 `ret = v > 0;` 及 `ret += v > 1;` 被檢查到 * e.g : v = 01110010 v > 0 => ret = 1 0111 | 0010 => 0111 、 ret = 1 + 4 01 | 11 => 01 、 ret = 1 + 4 + 2 01 <= 1 => ret = 1 + 4 + 2 = 7 ## 測驗八 ```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; // EXP14 q->data = (*r)->data; q = *r; *r = q->right; } delete q; } ``` * 題目為[二元搜尋樹](https://zh.wikipedia.org/zh-tw/%E4%BA%8C%E5%85%83%E6%90%9C%E5%B0%8B%E6%A8%B9) , 可知樹的 left child 比 parent 小 , 而樹的 right child 比 parent 大 * EXP12 及 EXP13 為二元搜尋樹尋找 data 的過程 , 根據 if 條件可知是要往左還往右 , 而 `p` 為 pointer to pointer 所以應該 assign 成 pointer 的地址 e.g:`&(*p)->left` ## 測驗九 考慮以下進行記憶體地址對齊 (alignment) 的巨集: ```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 - 1) & ~(MAX_ALIGNMENT - 1)) ``` * 由題目及程式碼可以推測 , 此巨集的功能為將給定的記憶體以 16 個 bit向上對齊 * `(((x) + MAX_ALIGNMENT - 1)` 確保新的記憶體位置大於等於原本的記憶體位置 , 達成向上對齊 * `& ~(MAX_ALIGNMENT - 1)` 將低位的 4 個 bit 清為 0 , 確保記憶體位置為 16 的倍數 撰寫出對應 ROUND_DOWN 巨集: ```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_DOWN_TO_ALIGNMENT_SIZE(x) \ (x & ~(MAX_ALIGNMENT - 1)) ``` * 只要將 `x` 低位的 4 個 bit 清為 0 就能確保記憶體位置為 16 的倍數 , 且新的記憶體位置也小於等於原記憶體位置 , 達成向下對齊 ## 測驗十 ```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)); \ }) ``` * DIVIDE_ROUND_CLOSEST 巨集會得到 `x` 除以 `divisor` 四捨五入後的結果 * 加上 `(__d >> 1)` 相當於讓 `(__x)/(__d)` 四捨五入 , 當 `(__x) >= 0.5*(__d)` 則 `(__d)-(__x)%(__d) <= (__d >> 1)` => 進位 , 又整數除法會無條件捨去小數 * `(((__x) > 0) == ((__d) > 0)))` 判斷結果為正或負 , 負數捨去小數點相當於取大的數 , 因此要改成減 `((__d) >> 1)` * `(((typeof(x)) -1) > 0 || ((typeof(divisor)) -1) > 0` 判斷 `x` 或 `divisor` 是否為無號數 , 若有無號數 , 因另一數也會被轉型成無號數 , 所以結果必為正數 ## 測驗十一 ```c static inline unsigned long fls(unsigned long word) { int num = 64 - 1; if (!(word & (~0ul << 32))) { num -= 32; word <<= 32; } if (!(word & (~0ul << (64 - 16)))) { num -= 16; word <<= 16; } if (!(word & (~0ul << (64 - 8)))) { num -= 8; word <<= 8; } if (!(word & (~0ul << (64 - 4)))) { num -= 4; word <<= 4; } if (!(word & (~0ul << (64 - 2)))) { num -= 2; word <<= 2; } if (!(word & (~0ul << (64 - 1)))) num -= 1; return num; } unsigned long i_sqrt(unsigned long x) { unsigned long b, m, y = 0; if (x <= 1) return x; m = 1UL << (fls(x) & ~1UL); while (m) { b = y + m; y >>= 1; //xxx if (x >= b) { x -= b; //yyy y += m; } m >>= 2; //zzz } return y; } ``` * 參考 [kdnvt 的筆記](https://hackmd.io/@kdnvt/linux2022-quiz3) , 將 `x` 拆為以下形式: * $x = (000b_0b_1b_2\cdots b_{n-1}b_{n})^2$ * $000b_0b_1b_2\cdots b_{n-1}b_{n}$ 為 bits pattern , $b_0$ 為最高位的 1 , $b_1$~$b_n$ 為 0 或 1 , $(000b_0b_1b_2\cdots b_{n-1}b_{n})$ 即所求的平方根 `y` * `fls` : 找出某數最高位的 bit 1 , 最低為 0 最高為 63 * `m = 1UL << (fls(x) & ~1UL)` : 題目平方根為向下取整 , 又 `y` 的平方最小一定為奇數個 bits , 因此當 `x` 最高位的 1 在偶數 bit 時( fls(x) 為奇數) , 要將 fls(x) 減一( `fls(x) & ~1UL`) , 確保 `y` 平方小於等於 `x` * while 迴圈為求得 `y` = $(b_0b_1b_2\cdots b_{n-1}b_{n})$ 的過程 , 從 $b_0$ 找到 $b_n$ , 其中 m 為 $b_i^2$ , y 為 $2*b_i*(b_{i-1} + \cdots + b_0)$(假設目前已找出$(b_0\cdots b_{i-1}xxx)$ , x為尚未找出的bit) , 而 `b = y + m` 則為 $b_i$ 為 1 時當前 $y_i^2$$(b_0\cdots b_{i-1}1000)$ 會多出上一個 $y_{i-1}^2$$(b_0\cdots b_{i-1}0000)$ 的值 , 當這個值大於 `x` 表示該 bit 應為 0 , 反之將 `x` 減去 `b` , `y` 加上 `m` * `m` 為 $b_i^2$ , 要找下一個 bit $b_{i+1}^2$ 會右移兩位 , `y` 為 $2*b_i*(b_{i-1} + \cdots + b_0)$ 要找下一個 bit $2*b_{i+1}*(b_{i} + \cdots + b_0)$ 會右移一位 *註 : $b_i$ >> 1 = $b_{i+1}$ [題目連結](https://hackmd.io/@sysprog/linux2022-quiz3#%E6%B8%AC%E9%A9%97-7)