---
# System prepended metadata

title: 2025q1 Homework2 (quiz1+2)

---

# 2025q1 Homework2 (quiz1+2)
contributed by < `chensheep` >

## 2025q1 第 1 週測驗題

### 測驗 `1`

![image](https://hackmd.io/_uploads/B1ssau0syx.png)

參考 `list_insert_before` 說明如上圖，是要在給定的 `before` 前加入新的 `item`。

考慮以下情形，目前 list 中有三個 items。

```graphviz
digraph DoublyLinkedList {
    rankdir=LR;
    // node [shape=record, fontname="Arial", style=filled, fillcolor=lightgray];
    node [shape=box, fontname="Arial", style=filled, fillcolor=lightgray];

    // start [label="head", color=green, fontcolor=green, fillcolor=lightblue];

    nodehead [label=<
        <table border="0" cellborder="1" cellpadding="5">
            <tr>
                <td PORT="p1" bgcolor="lightyellow"><font color="black">head</font></td>
            </tr>
        </table>
    >, fillcolor=lightyellow];

    node1 [label=<
        <table border="0" cellborder="1" cellpadding="5">
            <tr>
                <td PORT="p0" bgcolor="lightyellow">1</td>
            </tr>
            <tr>
                <td PORT="p1" bgcolor="lightyellow"><font color="black">next</font></td>
            </tr>
        </table>
    >, fillcolor=lightyellow];

    node2 [label=<
        <table border="0" cellborder="1" cellpadding="5">
            <tr>
                <td bgcolor="lightyellow">2</td>
            </tr>
            <tr>
                <td PORT="p1" bgcolor="lightyellow"><font color="black">next</font></td>
            </tr>
        </table>
    >, fillcolor=lightyellow];
    
    node3 [label=<
        <table border="0" cellborder="1" cellpadding="5">
            <tr>
                <td bgcolor="lightyellow">3</td>
            </tr>
            <tr>
                <td  PORT="p1" bgcolor="lightyellow"><font color="black">next</font></td>
            </tr>
        </table>
    >, fillcolor=lightyellow];

    end [label="NULL", color=black, fontcolor=black, fillcolor=lightyellow];

    nodehead:p1 -> node1;
    node1:p1 -> node2;
    node2:p1 -> node3;
    node3:p1 -> end;
    
}
```
假設目標是在 1 之前加入一個新的 item 4 ，可以觀察到需要修改部份標注成紅色。
```graphviz
digraph DoublyLinkedList {
    rankdir=LR;
    // node [shape=record, fontname="Arial", style=filled, fillcolor=lightgray];
    node [shape=box, fontname="Arial", style=filled, fillcolor=lightgray];

    // start [label="head", color=green, fontcolor=green, fillcolor=lightblue];

    nodehead [label=<
        <table border="0" cellborder="1" cellpadding="5">
            <tr>
                <td PORT="p1" bgcolor="lightpink"><font color="black">head</font></td>
            </tr>
        </table>
    >, fillcolor=lightyellow];

    node1 [label=<
        <table border="0" cellborder="1" cellpadding="5">
            <tr>
                <td PORT="p0" bgcolor="lightyellow">1</td>
            </tr>
            <tr>
                <td PORT="p1" bgcolor="lightyellow"><font color="black">next</font></td>
            </tr>
        </table>
    >, fillcolor=lightyellow];

    node2 [label=<
        <table border="0" cellborder="1" cellpadding="5">
            <tr>
                <td bgcolor="lightyellow">2</td>
            </tr>
            <tr>
                <td PORT="p1" bgcolor="lightyellow"><font color="black">next</font></td>
            </tr>
        </table>
    >, fillcolor=lightyellow];
    
    node3 [label=<
        <table border="0" cellborder="1" cellpadding="5">
            <tr>
                <td bgcolor="lightyellow">3</td>
            </tr>
            <tr>
                <td  PORT="p1" bgcolor="lightyellow"><font color="black">next</font></td>
            </tr>
        </table>
    >, fillcolor=lightyellow];

    node4 [label=<
        <table border="0" cellborder="1" cellpadding="5">
            <tr>
                <td bgcolor="lightyellow">4</td>
            </tr>
            <tr>
                <td  PORT="p1" bgcolor="lightpink"><font color="black">next</font></td>
            </tr>
        </table>
    >, fillcolor=lightyellow];

    end [label="NULL", color=black, fontcolor=black, fillcolor=lightyellow];

    nodehead:p1 -> node1;
    node1:p1 -> node2;
    node2:p1 -> node3;
    node3:p1 -> end;
    
}
```
因此這邊拿一個指針 p 指到要修改的記憶體位置，如下圖，這樣可以同時擁有要修改的位置 p 與指到當前需被比較的 *p。
```graphviz
digraph DoublyLinkedList {
    rankdir=LR;
    // node [shape=record, fontname="Arial", style=filled, fillcolor=lightgray];
    node [shape=box, fontname="Arial", style=filled, fillcolor=lightgray];

    // start [label="head", color=green, fontcolor=green, fillcolor=lightblue];

    nodehead [label=<
        <table border="0" cellborder="1" cellpadding="5">
            <tr>
                <td PORT="p1" bgcolor="lightpink"><font color="black">head</font></td>
            </tr>
        </table>
    >, fillcolor=lightyellow];

    node1 [label=<
        <table border="0" cellborder="1" cellpadding="5">
            <tr>
                <td PORT="p0" bgcolor="lightyellow">1</td>
            </tr>
            <tr>
                <td PORT="p1" bgcolor="lightyellow"><font color="black">next</font></td>
            </tr>
        </table>
    >, fillcolor=lightyellow];

    node2 [label=<
        <table border="0" cellborder="1" cellpadding="5">
            <tr>
                <td bgcolor="lightyellow">2</td>
            </tr>
            <tr>
                <td PORT="p1" bgcolor="lightyellow"><font color="black">next</font></td>
            </tr>
        </table>
    >, fillcolor=lightyellow];
    
    node3 [label=<
        <table border="0" cellborder="1" cellpadding="5">
            <tr>
                <td bgcolor="lightyellow">3</td>
            </tr>
            <tr>
                <td  PORT="p1" bgcolor="lightyellow"><font color="black">next</font></td>
            </tr>
        </table>
    >, fillcolor=lightyellow];

    node4 [label=<
        <table border="0" cellborder="1" cellpadding="5">
            <tr>
                <td bgcolor="lightyellow">4</td>
            </tr>
            <tr>
                <td  PORT="p1" bgcolor="lightpink"><font color="black">next</font></td>
            </tr>
        </table>
    >, fillcolor=lightyellow];

    end [label="NULL", color=black, fontcolor=black, fillcolor=lightyellow];

    p [label=<
        <table border="0" cellborder="1" cellpadding="5">
            <tr>
                <td PORT="p1" bgcolor="lightblue"><font color="black">p</font></td>
            </tr>
        </table>
    >, fillcolor=lightyellow];

    nodehead:p1 -> node1;
    node1:p1 -> node2;
    node2:p1 -> node3;
    node3:p1 -> end;
    p:p1 -> nodehead:p1;
    
}
```
觀察 `list_insert_before` 函式
```c=
{
    list_item_t **p;
    for (p = AAAA; *p != BBBB; p = CCCC)
        ;
    *p = item;
    DDDD = before;
}
```
第 5 行對應流程如下
```graphviz
digraph DoublyLinkedList {
    rankdir=LR;
    // node [shape=record, fontname="Arial", style=filled, fillcolor=lightgray];
    node [shape=box, fontname="Arial", style=filled, fillcolor=lightgray];

    // start [label="head", color=green, fontcolor=green, fillcolor=lightblue];

    nodehead [label=<
        <table border="0" cellborder="1" cellpadding="5">
            <tr>
                <td PORT="p1" bgcolor="lightpink"><font color="black">head</font></td>
            </tr>
        </table>
    >, fillcolor=lightyellow];

    node1 [label=<
        <table border="0" cellborder="1" cellpadding="5">
            <tr>
                <td PORT="p0" bgcolor="lightyellow">1</td>
            </tr>
            <tr>
                <td PORT="p1" bgcolor="lightyellow"><font color="black">next</font></td>
            </tr>
        </table>
    >, fillcolor=lightyellow];

    node2 [label=<
        <table border="0" cellborder="1" cellpadding="5">
            <tr>
                <td bgcolor="lightyellow">2</td>
            </tr>
            <tr>
                <td PORT="p1" bgcolor="lightyellow"><font color="black">next</font></td>
            </tr>
        </table>
    >, fillcolor=lightyellow];
    
    node3 [label=<
        <table border="0" cellborder="1" cellpadding="5">
            <tr>
                <td bgcolor="lightyellow">3</td>
            </tr>
            <tr>
                <td  PORT="p1" bgcolor="lightyellow"><font color="black">next</font></td>
            </tr>
        </table>
    >, fillcolor=lightyellow];

    node4 [label=<
        <table border="0" cellborder="1" cellpadding="5">
            <tr>
                <td bgcolor="lightyellow">4</td>
            </tr>
            <tr>
                <td  PORT="p1" bgcolor="lightpink"><font color="black">next</font></td>
            </tr>
        </table>
    >, fillcolor=lightyellow];

    end [label="NULL", color=black, fontcolor=black, fillcolor=lightyellow];

    p [label=<
        <table border="0" cellborder="1" cellpadding="5">
            <tr>
                <td PORT="p1" bgcolor="lightblue"><font color="black">p</font></td>
            </tr>
        </table>
    >, fillcolor=lightyellow];

    nodehead:p1 -> node1 [style=dotted];
    node1:p1 -> node2;
    node2:p1 -> node3;
    node3:p1 -> end;
    p:p1 -> nodehead:p1;
    nodehead:p1 -> node4 [color=red];
    
}
```
第 6 行對應流程如下，所以 DDDD 為 item->next
```graphviz
digraph DoublyLinkedList {
    rankdir=LR;
    // node [shape=record, fontname="Arial", style=filled, fillcolor=lightgray];
    node [shape=box, fontname="Arial", style=filled, fillcolor=lightgray];

    // start [label="head", color=green, fontcolor=green, fillcolor=lightblue];

    nodehead [label=<
        <table border="0" cellborder="1" cellpadding="5">
            <tr>
                <td PORT="p1" bgcolor="lightpink"><font color="black">head</font></td>
            </tr>
        </table>
    >, fillcolor=lightyellow];

    node1 [label=<
        <table border="0" cellborder="1" cellpadding="5">
            <tr>
                <td PORT="p0" bgcolor="lightyellow">1</td>
            </tr>
            <tr>
                <td PORT="p1" bgcolor="lightyellow"><font color="black">next</font></td>
            </tr>
        </table>
    >, fillcolor=lightyellow];

    node2 [label=<
        <table border="0" cellborder="1" cellpadding="5">
            <tr>
                <td bgcolor="lightyellow">2</td>
            </tr>
            <tr>
                <td PORT="p1" bgcolor="lightyellow"><font color="black">next</font></td>
            </tr>
        </table>
    >, fillcolor=lightyellow];
    
    node3 [label=<
        <table border="0" cellborder="1" cellpadding="5">
            <tr>
                <td bgcolor="lightyellow">3</td>
            </tr>
            <tr>
                <td  PORT="p1" bgcolor="lightyellow"><font color="black">next</font></td>
            </tr>
        </table>
    >, fillcolor=lightyellow];

    node4 [label=<
        <table border="0" cellborder="1" cellpadding="5">
            <tr>
                <td bgcolor="lightyellow">4</td>
            </tr>
            <tr>
                <td  PORT="p1" bgcolor="lightpink"><font color="black">next</font></td>
            </tr>
        </table>
    >, fillcolor=lightyellow];

    end [label="NULL", color=black, fontcolor=black, fillcolor=lightyellow];

    p [label=<
        <table border="0" cellborder="1" cellpadding="5">
            <tr>
                <td PORT="p1" bgcolor="lightblue"><font color="black">p</font></td>
            </tr>
        </table>
    >, fillcolor=lightyellow];

    node1:p1 -> node2;
    node2:p1 -> node3;
    node3:p1 -> end;
    p:p1 -> nodehead:p1;
    nodehead:p1 -> node4;
    node4:p1 -> node1 [color=red];
}
```

回到 `list_insert_before` 函式第 3 行， p 一開始指到 list 存放指到第一個 item 指標的位置，因此為 AAAA &l->head。
*p 表示當前的 item 當當前的 item 為目標 before 則離開迴圈，因此 BBBB 為 before。
最後要更新 `p` 為當前 item 存放指向下一個節點指標的位址，所以 CCCC 為 &(*p)->next。

#### 延伸問題

##### 解釋上方程式碼運作原理

##### 在現有的程式碼基礎上，撰寫合併排序操作

### 測驗 `2`

針對本段程式碼
```c=
/* Find the in-order predecessor:
 * This is the rightmost node in the left subtree.
 */
block_t **pred_ptr = &(*node_ptr)->l;
while (EEEE)
    pred_ptr = FFFF;
```
閱讀註解可知目標是找到左子樹中最右邊的節點。
所以當此節點沒有右邊的節點即為目標，因此 EEEE 為 (*pred_ptr)->r。
當此節點有右節點時，更新 pred_ptr 為當前節點指向右邊節點的位址，因此 FFFF 為 &(*pred_ptr)->r。

嘗試編譯 [memory-allocators](https://github.com/mtrebi/memory-allocators)

```shell
sudo apt  install cmake
cmake -S memory-allocators -B build
cmake --build build 
```

### 測驗 `3`

題目中 rebuild_list_link 的目的為給定一個 list 重新將這個 list 中每個節點的 prev 指針重建指向正確的節點。
因為在進行 quick_sort 排序時，我們忽略維護每個節點的 prev 指針，所以排序完成後需對此函式 list 重建 prev 指針。
rebuild_list_link 函式中使用 node 與 prev 去紀錄當前的節點與其前一個節點，以此每次將 node 的 prev 更新，並移動到下一個節點。
最後 node 為 NULL 時離開迴圈，此時 prev 紀錄最後一個節點，因此將其 next 指向 head，並將 head 的 prev 指向其。
即 GGGG 為 head->prev=prev。

接著分析 quick_sort 函式，begin[i] 為目前要排序的 list。
假如 L 等於 R 表示 0 個或 1 個節點，代表排序完成，將這個成果放到 result list 中。
反之代表這個 list 未完成排列，取出其第一個節點作為 pivot，
在透過 list_entry 取出值，因此 HHHH 為 list_entry(pivot,node_t,list)->value。
接著將剩餘的節點依序跟 pivot 的值相比，小於的放到 left 大於的放到 right 相反的放到 left。
分別紀錄這三個 list 到 begin[i]，begin[i+1]，begin[i+2]，因此 JJJJ 與 KKKK 分別為 pivot 與 right。
將 i + 2 表示我們先選擇 begin[i+2] 為下輪要排序的 list。

以下進行簡單的演示

9 5 8 6 1 10

0 1 6 8 5
1 9
2 10

result 9 10

0 
1 1
2 5 8 6

0 
1 1
2 
3 5
4 6 8

0 
1 1
2 
3 5
4 
5 6
6 8

1 5 6 8 9 10

GGGG 為有效 C 語言表示式，不含 ; 或 , 字元
HHHH 和 IIII 包含 list_entry 巨集，不含 ; 字元
JJJJ 和 KKKK 為有效 C 語言表示式，不含 ; 或 , 字元
以最精簡的方式撰寫，不包含空白

list_entry(head->next, node_t, list)->value;

GGGG: head->prev=prev
HHHH: list_entry(pivot,node_t,list)->value
IIII: list_entry(n,node_t,list)->value
JJJJ: pivot
KKKK: right

延伸問題:

解釋上述程式碼運作原理
研讀〈A comparative study of linked list sorting algorithms〉，分析針對雙向鏈結串列的排序演算法考量，並運用 Linux 核心風格的 List API 予以實作

針對 rebuild_list_link
```c
static void rebuild_list_link(struct list_head *head)
{
    if (!head)
        return;
    struct list_head *node, *prev;
    prev = head;
    node = head->next;
    while (node) {
        node->prev = prev;
        prev = node;
        node = node->next;
    }
    prev->next = head;
    head->prev = prev;
}
```


## 2025q1 第 2 週測驗題

### 測驗 1

快速排序函式的原型宣告如下
```c
static void list_quicksort(struct list_head *head);
```
在 list_quicksort 中本次透過 recursive 方式實現。
在函式 list_quicksort 我們宣告 list_less 與 list_greater 分別用來放置比 pivot 小與大的節點。
如果傳入的 list 為空的或是只有一個節點表示排序完畢。
反之進行排序，這邊取出第一個節點，可以透過 list_first_entry ，因此 AAAA 為 list_first_entry。
取出第一個節點後將其從 list 移除，因此 BBBB 為 list_del。
接著針對剩餘的節點與 pivo 的值進行比較，較小者加入到 list_less 中，反之加入到 list_greater 中。
這邊 CCCC 為 list_move_tail 主要是考量到為符合 stable sorting ，所以先被比較到的節點，其應該放置在 list_greater 前面，所以後來被比較到的節點 list_move_tail 放置到 list_greater 的尾端，可以維持其原本的相對位置。
最後將排序完成的 pivot->list，list_less 與 list_greater 重新放回 head 中。
因為 pivot 為單一節點，所以使用 list_add 即可，因此 DDDD 為 list_add。
list_less 與 list_greater 中有可能有多個節點，因此要透過其他方式。
list_less list 需要放置到 head 的前端，因此透過 list_splice 可以將一個 list 的所有節點放到另一個 list 的前端，EEEE 為 list_splice。
list_less list 需要放置到 head 的後端，因此透過 list_splice_tail 可以將一個 list 的所有節點放到另一個 list 的後端，FFFF 為 list_splice_tail。
EEEE: list_splice()
FFFF: list_splice_tail()

延伸問題：

解釋上方程式碼運作原理，改進 random_shuffle_array 也探討 list_for_each_entry_safe 建構的迴圈中，若將 list_move_tail 換為 list_move，為何會無法滿足 stable sorting
將程式碼整合進 lab0 提及的 qtest，並探討環狀雙向鏈結串列的排序效能，並善用 perf 一類的效能分析工具，探討包含 Linux 核心 list_sort 在內排序實作的效能表現並充分解釋

MMMM: ~1ULL
1
2

### 測驗 2

### 測驗 3

對應到
![image](https://hackmd.io/_uploads/rkaWnfFayx.png)
find_key 函式實做如下
```c
static struct hash_key *find_key(map_t *map, int key)
{
    struct hlist_head *head = &(map->ht)[hash(key, AAAA)];
    for (struct hlist_node *p = head->first; p; p = p->next) {
        struct hash_key *kn = container_of(p, struct hash_key, node);
        if (kn->key == key)
            return kn;
    }
    return NULL;
}
```
hash 的 bit 大小在初始化 map_t 結構時即已經帶入，因此從 map_t 結構中可以取出 AAAA 為 map->bits。
相對應的 BBBB 同樣為 map->bits。
新增操作： (部分遮蔽)
```c
void map_add(map_t *map, int key, void *data)
{
    struct hash_key *kn = find_key(map, key);
    if (kn)
        return;

    kn = malloc(sizeof(struct hash_key));
    kn->key = key, kn->data = data;

    struct hlist_head *h = &map->ht[hash(key, BBBB)];
    struct hlist_node *n = &kn->node, *first = h->first;

    n->next = first;
    if (first)
        CCCC = &n->next;
    h->first = n;
    DDDD = &h->first;
}
```
這邊的目標是將新增的 hlist_node 放置到這個 list 的最前方。
因此首先將新增的 node 其 next 指向目前的 first，進一步判斷 first 存在的話，需將期 pprev 修改為新增的 node 的 next 位址。
因此 CCCC 為 first->pprev。
另外需將新增的 node 其 pprev 指到 hlist_head.first 因此 DDDD 為 n->pprev。
接著觀察釋放記憶體的操作:
```c
void map_deinit(map_t *map)
{
    if (!map)
        return;

    for (int i = 0; i < MAP_HASH_SIZE(map->bits); i++) {
        struct hlist_head *head = &map->ht[i];
        for (struct hlist_node *p = head->first; p;) {
            struct hash_key *kn = container_of(p, struct hash_key, node);
            struct hlist_node *n = p;
            p = p->next;

            if (!n->pprev) /* unhashed */
                goto bail;

            struct hlist_node *next = n->next, **pprev = EEEE;
            *pprev = next;
            if (next)
                next->pprev = pprev;
            n->next = NULL, n->pprev = NULL;

        bail:
            free(kn->data);
            free(kn);
        }
    }
    free(map);
}
```

## Reference
