---
# System prepended metadata

title: 2022q1 Homework3 (quiz3)

---

# 2022q1 Homework3 (quiz3)
contributed by < `Kevin-Shih` >

## 測驗 1

**題目**

在 Linux 核心原始程式碼，[include/linux/bitfield.h](https://github.com/torvalds/linux/blob/master/include/linux/bitfield.h) 提及一個巨集 `GENMASK`，其作用是依據給定的範圍，產生連續的 bitmask，例如:

- `GENMASK(6, 4)` 產生 01110000~2~
- `GENMASK(39, 21)` 產生 0x000000ffffe00000 (64 位元)

已知我們使用的微處理器架構為 64 位元，且 `unsigned long` 為 8 位元組寬度 (符合 LP64 資料模型)，以下是可能的 `GENMASK` 巨集的定義:
```c 
#define GENMASK(h, l) \
    (((~0UL) >> (LEFT)) & ((~0UL) >> (l) << (RIGHT)))
```

請補完，使其行為符合預期。

#### 運作原理與解題

`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 以避免此情形發生。

:::warning
TODO
- 比較 Linux 核心 `GENMASK` 巨集的實作，闡述其額外的考量
- 舉出 Linux 核心原始程式碼中二處 `GENMASK` 巨集和 [include/linux/bitfield.h](https://github.com/torvalds/linux/blob/master/include/linux/bitfield.h) 的應用案例
:::
#### 比較 Linux 核心 `GENMASK` 巨集的實作，闡述其額外的考量
#### 舉出 Linux 核心原始程式碼中二處 `GENMASK` 巨集和 [include/linux/bitfield.h](https://github.com/torvalds/linux/blob/master/include/linux/bitfield.h) 的應用案例

## 測驗 2

**題目**

考慮以下程式碼:
```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){EXP1, v & 3};
}
```
函式 `fo_fd` 接受指定的地址 `v`，填入一個 `fd` 結構體，並確保第一個成員的地址得以依據〈[你所不知道的 C 語言：記憶體管理、對齊及硬體特性](https://hackmd.io/@sysprog/c-memory)〉描述，對 4 個位元組進行向下對齊 (即 C++ Boost 描述的 [align_down](https://www.boost.org/doc/libs/1_78_0/doc/html/align/reference.html#align.reference.functions.align_down))。其中 `struct foo;` 運用〈[你所不知道的 C 語言：指標篇](https://hackmd.io/@sysprog/c-pointer)〉提及的 forward declaration 技巧，可在實作階段才提供具體 `struct foo` 的定義。

請補完，使其行為符合預期。

#### 運作原理與解題
由於 `fd` 結構體的第一個成員是指向 `struct foo` 結構體的指標，因此 `EXP1` 肯定要將 `unsigned long` 型態的 `v` casting 成指向 `struct foo` 型態的指標，即 `EXP1` 前面一定包含 `(struct foo *)`。

接著由於要作 4 byte 的 align down 因此代表 2^1^, 2^0^ 的最低位 2 bits 應設為 0 (低位的 log~2~(4) 個 bits 設為 0)，而這可以透過 `AND` 一個最低 2 位為 0 的 bitmask 達成，最簡潔的寫法是透過取 `3` 的 1 補數達成。

因此 `EXP1` = `(struct foo *)(v & ~3)`

:::warning
TODO
- 在 Linux 核心原始程式碼找出類似技巧的實作 (例如檔案系統) 並解說
:::
#### 在 Linux 核心原始程式碼找出類似技巧的實作 (例如檔案系統) 並解說

## 測驗 3

**題目**

考慮以下程式碼，能將輸入的 8 位元無號整數逐一位元反轉，如同 [LeetCode 190. Reverse Bits](https://leetcode.com/problems/reverse-bits/) 的描述。
```c= 
#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;
}
```
請補完，使其行為符合預期。

#### 運作原理與解題
進行 `rev8` 前的一個 `uint8_t` 示意圖
```graphviz 
digraph  "reverse-bits"  {
    node [shape=record];
    rankdir=LR;
    a [label="{ 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 }"];
}
```
> 為求方便辨識原本的 0 到 7 bit 分別以 0 到 7 標注

#2 將高位 4 bits 與低位 4 bits 對調後
```graphviz 
digraph  "reverse-bits"  {
    node [shape=record];
    rankdir=LR;
    a [label="{ 3 | 2 | 1 | 0 | 7 | 6 | 5 | 4 }"];
}
```
#3 左半邊可以看出是將 4 bits 內的高位 2 bits 調至低位 2 bits，因此不難猜出 `EXP2` 是將 4 bits 內的低位 2 bits 調至高位 2 bits，調換後的示意圖如下
```graphviz 
digraph  "reverse-bits"  {
    node [shape=record];
    rankdir=LR;
    a [label="{ 1 | 0 | 3 | 2 | 5 | 4 | 7 | 6 }"];
}
```
#4 的左半邊當然就是將 2 bits 內的高位 bit 調至低位 bit，因此 `EXP3` 是將 2 bits 內的低位 bit 調至高位 bit，調換後的示意圖如下
```graphviz 
digraph  "reverse-bits"  {
    node [shape=record];
    rankdir=LR;
    a [label="{ 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 }"];
}
```
由上面的步驟得出答案為
- `EXP2` = `(x & 0x33) << 2`
- `EXP3` = `(x & 0x55) << 1`
> `0x33` = `0b00110011`, `0x55` = `0b01010101`
:::warning
TODO
- 在 Linux 核心原始程式碼找出類似技巧的實作並解說其應用場景
:::

#### 在 Linux 核心原始程式碼找出類似技巧的實作並解說其應用場景

## 測驗 4

**題目**

延伸〈[你所不知道的 C 語言：前置處理器應用篇](https://hackmd.io/@sysprog/c-preprocessor)〉，我們嘗試將 [foreach 巨集](https://en.wikipedia.org/wiki/Foreach_loop) 予以擴充，使得支援以下寫法:
```c 
    int i;
    foreach_int(i, 0, -1, 100, 0, -99) {
        printf("i is %i\n", i);
    }
    const char *p;
    foreach_ptr(p, "Hello", "world") {
        printf("p is %s\n", p);
    }
```
預期輸出如下:
``` 
i is 0
i is -1
i is 100
i is 0
i is -99
p is Hello
p is world
```
對應的巨集定義:
```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})[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__})))
```
請補完，使其行為符合預期。

#### 運作原理與解題
從程式碼可以看出不論是 `foreach_int`, `foreach_ptr` 中的 `_foreach_i` 都是用來標示讀到第幾個 `__VA_ARGS__` 的，因此每次迴圈 `foreach_int` 應加 1，而由於每次迴圈的更新要對 `i` 賦值，使其值等於下個 `__VA_ARGS__` 中的值，因此 `EXP4`, `EXP5`  = `++_foreach_i`，而非 `_foreach_i++`。 (先更新 `foreach_int` 再對 `i` 賦值)

:::warning
TODO
- 在 Linux 核心原始程式碼找出類似技巧的實作並解說其應用場景

:::

#### 在 Linux 核心原始程式碼找出類似技巧的實作並解說其應用場景


## 測驗 5

**題目**

針對 [LeetCode 29. Divide Two Integers](https://leetcode.com/problems/divide-two-integers/)，以下是可能的實作:
```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;
}
```

請補完，使其行為符合預期。

#### 運作原理與解題
#5 ~ #15 用於處理當除數或被除數有負值的時候將其轉為正值後存入對應的 `unsigned int`，並將其正負號紀錄於 `signal` 變數，方便後續運算。

#18 則是當被除數大於除數乘上 2^shift^ 時，代表商的最高位的 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` 即為答案。

:::warning
TODO
- 指出上述程式碼可改進之處，並予以實作
- 在 Linux 核心原始程式碼中找出針對整數/浮點數/定點數除法特別撰寫的程式碼，並予以討論
:::

#### 指出上述程式碼可改進之處，並予以實作
#### 在 Linux 核心原始程式碼中找出針對整數/浮點數/定點數除法特別撰寫的程式碼，並予以討論

## 測驗 6

**題目**

針對 [LeetCode 149. Max Points on a Line](https://leetcode.com/problems/max-points-on-a-line/)，以下是可能的實作:
```c 
#include <stdbool.h>
#include "list.h"

struct Point {
    int x, y;
};

struct point_node {
    int p1, p2;
    struct list_head link;
};

static bool can_insert(struct list_head *head, int p1, int p2)
{
    struct point_node *pn;
    list_for_each_entry (pn, head, link)
        return EXP8;
    return true;
}

static int gcd(int x, int y)
{
    while (y) {
        int tmp = y;
        y = x % y;
        x = tmp;
    }
    return x;
}

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;
                    EXP9;
                }
            }
        }
    }

    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;
}
```
請補完，使其行為符合預期。

#### 運作原理與解題

:::warning
TODO
- 指出上述程式碼可改進之處，並予以實作
- 擴充 LeetCode 題目，考慮更多座標點的輸入 (例如超過 10 萬個) 時，設計有效的資料結構以降低記憶體開銷，並確保快速的執行
:::

#### 指出上述程式碼可改進之處，並予以實作
#### 擴充 LeetCode 題目，考慮更多座標點的輸入 (例如超過 10 萬個) 時，設計有效的資料結構以降低記憶體開銷，並確保快速的執行

## 測驗 7

**題目**

考慮 `ilog32` 函式可針對給定的 32 位元無號數，計算扣除開頭的 `0`，最少需要多少位元才可儲存 (the minimum number of bits required to store an unsigned 32-bit value without any leading zero bits)，以下是一個可能的實作:
```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 = EXP10;
    v >>= m;
    ret |= m;
    EXP11;
    return ret;
}
```
請補完，使其行為符合預期。

#### 運作原理與解題
#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` 。 

:::warning
TODO
- 在 Linux 核心原始程式碼找出類似實作並解說其應用場景
- 研讀論文《[Using de Bruijn Sequences to Index a 1 in a Computer Word](http://supertech.csail.mit.edu/papers/debruijn.pdf)》，探討缺乏硬體 ctz/clz 指令的微處理器上，如何實作 branchless 的 `ilog`
- 運用〈[你所不知道的 C 語言：前置處理器應用篇](https://hackmd.io/@sysprog/c-preprocessor)〉和〈[你所不知道的 C 語言：數值系統篇](https://hackmd.io/@sysprog/c-numerics)〉提及的技巧，實作編譯時期 (compile-time) 的 `ilog2` 實作
:::

#### 在 Linux 核心原始程式碼找出類似實作並解說其應用場景
#### 研讀論文《[Using de Bruijn Sequences to Index a 1 in a Computer Word](http://supertech.csail.mit.edu/papers/debruijn.pdf)》，探討缺乏硬體 ctz/clz 指令的微處理器上，如何實作 branchless 的 `ilog`
#### 運用〈[你所不知道的 C 語言：前置處理器應用篇](https://hackmd.io/@sysprog/c-preprocessor)〉和〈[你所不知道的 C 語言：數值系統篇](https://hackmd.io/@sysprog/c-numerics)〉提及的技巧，實作編譯時期 (compile-time) 的 `ilog2` 實作

## 測驗 8

**題目**

考慮以下 C++ 二元搜尋樹的程式:
```cpp 
typedef struct tnode *tree;
struct tnode {
    int data;   
    tnode *left;
    tnode *right;
    tnode(int d)
    {       
        data = d;
        left = right = 0;
    }           
};

void remove_data(tree &t, int d)
{
    tnode *p = t;
    tnode *q;
    while (p != 0 && p->data != d) {
        if (d < p->data) {
            q = p;
            p = p->left;
        } else {
            q = p;
            p = p->right;
        }
    }
    if (p != 0) {
        if (p->left == 0)
            if (p == t)
                t = p->right;
            else if (p == q->left)
                q->left = p->right;
            else
                q->right = p->right;
        else if (p->right == 0)
            if (p == t)
                t = p->left;
            else if (p == q->left)
                q->left = p->left;
            else
                q->right = p->left;
        else {
            tnode *r = p->right;
            while (r->left) {
                q = r;
                r = r->left;
            }
            p->data = r->data;
            if (r == p->right)
                p->right = r->right;
            else
                q->left = r->right;
            p = r;
        }
        delete p;
    }
}
```
函式 `remove_data` 嘗試將指定的資料，自給定的二元搜尋樹移除，但原本的 `remove_data` 過於冗長，於是我們可善用 indirect pointer 改寫為以下:
```cpp  
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;
}
```
請補完，使其行為符合預期。

#### 運作原理與解題
前面的 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 了。

**沒有左子樹或右子樹時**
```cpp 
if (!q->left)
    *p = q->right;
else if (!q->right)
    *p = q->left;
```
當被移除的 node 沒有左子樹時直接以右子樹取代該 node。 反之若沒有右子樹時直接以左子樹取代該 node。

**左右子樹都存在時**
```cpp 
tnode **r = &q->right;
while ((*r)->left)
    r = EXP14;
q->data = (*r)->data;
q = *r;
*r = q->right;
```
這邊是從右子樹中找最小的 node 取代要移除的 node。 先以 `r` 指向要移除的 node 的 right，當該 node 有左子葉時 `r` 就指向 `(*r)->left` 所在的記憶體位置，一路找到沒有左子葉時就是右子樹中最小的 node。 接著將這個 node 的 data 複製到要刪除的 node 中取代要被刪除的值，並將 `q` 指向該 node，並將 `*r` 指向 `q` 的右子樹。 因此 `EXP14` = `&(*r)->left`。

最後刪除 `q` 即可。

:::warning
TODO
- 以 C99 改寫上述程式碼，並寫出更精簡高效的實作，和最初的實作相比，探討效率的提升
- 研讀 [Static B-Trees](https://en.algorithmica.org/hpc/data-structures/s-tree/)，探討針對記憶體佈局 (memory layout) 的改進策略
:::

#### 以 C99 改寫上述程式碼，並寫出更精簡高效的實作，和最初的實作相比，探討效率的提升
#### 研讀 [Static B-Trees](https://en.algorithmica.org/hpc/data-structures/s-tree/)，探討針對記憶體佈局 (memory layout) 的改進策略