--- tags: linux2023 --- # 2023q1 Homework1 (quiz1) contributed by < [chiangkd](https://github.com/chiangkd) > ## 開發環境 ```shell $ gcc --version gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0 Copyright (C) 2021 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 39 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 8 On-line CPU(s) list: 0-7 Vendor ID: GenuineIntel Model name: Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz CPU family: 6 Model: 158 Thread(s) per core: 2 Core(s) per socket: 4 Socket(s): 1 Stepping: 9 CPU max MHz: 3800.0000 CPU min MHz: 800.0000 BogoMIPS: 5599.85 ``` ## 測驗一 **延伸問題** - [ ] 解釋程式碼運作原理 - [ ] 針對 [Quick sort](https://en.wikipedia.org/wiki/Quicksort) 提出程式碼的改進方案並實作 - [ ] 引入 hybrid sort 策略並在 Linux 核心風格的 circular doubly-linked list 實作,需要量化性能表現並探討不同資料集 (data set) 的影響 - [ ] ==BONUS==: 嘗試對 Linux 核心提出排序程式碼改進的貢獻 <s>快速排序法原本並不是一個 stable sort ,而是 unstable sort</s> 根據 [Wikipedia](https://en.wikipedia.org/wiki/Quicksort),一般常見的 quicksort 並不是 stable sort,例如 [Lomuto partition scheme](https://en.wikipedia.org/wiki/Quicksort#Lomuto_partition_scheme),[Hoare partition scheme](https://en.wikipedia.org/wiki/Quicksort#Hoare_partition_scheme),但是也可以透過進行 [out-of-place](https://en.wikipedia.org/wiki/In-place_algorithm),進行 stable 版本的 quick sort 實作,與一般需要 $O(nlogn)$ 空間複雜度的 in-place 版本不同,out-of-place 需要 $O(n)$ 的空間來儲存資訊,文中也提到可以透過 linked list 實作 stable sort 版本的 quick sort,但是此時是否有 [random access](https://en.wikipedia.org/wiki/Random_access) 就會非常重要。 >Although quicksort can be implemented as a stable sort using linked lists, it will often suffer from poor pivot choices without random access. :::warning 這句不精準,其實不是演算法設計的問題,而是實作手段,可參照 Wikipedia (要看英文版,避免讀中文詞條,後者充斥許多錯誤) 的解釋。 :notes: jserv >Fix it, Thanks! ::: **結構體** ```c struct item { uint16_t i; struct list_head list; }; ``` ```c struct item *pivot = list_first_entry(head, AAA, BBB); list_del(&pivot->list); ``` 考慮到結構體的定義、 `list_first_entry` 的用途以及變數的宣告,可知 `AAA` = `item`,`BBB` = `list` ```c CCC (itm, is, head, list) { if (cmpint(&itm->i, &pivot->i) < 0) DDD (&itm->list, &list_less); else EEE (&itm->list, &list_greater); } ``` 根據 [list.h](https://github.com/sysprog21/lab0-c/blob/master/list.h) 中的定義,可以放入 4 個引數的僅有 `list_for_each_entry_safe` = `CCC` ,且 `DDD`、`EEE` 負責根據當前迭代到的值大於或是小於 `pivot` 來進行節點交換 - 當迭代到的值小於 `pivot` 的值 (`if` 條件滿足) 則將該節點移動至 `list_less` 節點後面,若否,則移動到 `list_greater` 節點後面 - `DDD` = `EEE` = `list_move_tail` 透過遞迴的方式繼續對 `list_less`、 `list_greater` 做一樣的處理,此時注意到最後兩行為 ```c list_add(&pivot->list, head); list_splice(&list_less, head); FFF(&list_greater, head) ``` 代表每次回傳都是將已經排序好的 list 接在 `head` 後面,其中,首先將 `pivot` 放在 `head` 後,再將代表比 `pivot` 小的 `list_less` 放在 `head` 後面,最後一部將比 `pivot` 大的 `list_greater` 放在 `pivot` 右側,也就是目前由 `head` 開頭的 list 的最後面,故得知 `list_splice_tail` = `FFF`. ```graphviz digraph g{ node [shape=box]; pivot[shape=plaintext] head[label=head][shape=circle] 5[label=5]; 4[label=4]; 2[label=2]; 3[label=3]; 1[label=1]; less[label=less][shape=plaintext] greater[label=greater][shape=plaintext] none1[label=""][shape=plaintext] none2[label=""][shape=plaintext] subgraph cluster_0 { style=invis; { rank=same; head->5->4->2->3->1; } } pivot -> 5; subgraph less { { rank=same; less->none1; } } subgraph greater { { rank=same; greater->none2 } } } ``` ```c struct item *pivot = list_first_entry(head, AAA, BBB); list_del(&pivot->list); ``` ```graphviz digraph g{ node [shape=box]; pivot[shape=plaintext] head[label=head][shape=circle] 5[label=5]; 4[label=4]; 2[label=2]; 3[label=3]; 1[label=1]; less[label=less][shape=plaintext] greater[label=greater][shape=plaintext] none1[label=""][shape=plaintext] none2[label=""][shape=plaintext] subgraph cluster_0 { style=invis; { rank=1; head->4->2->3->1; } } pivot -> 5; subgraph less { { rank=1; less->none1; } } subgraph greater { { rank=1; greater->none2 } } } ``` ```c struct item *itm = NULL, *is = NULL; list_for_each_entry_safe (itm, is, head, list) { if (cmpint(&itm->i, &pivot->i) < 0) list_move_tail(&itm->list, &list_less); else list_move_tail(&itm->list, &list_greater); } ``` ```graphviz digraph g{ node [shape=box]; pivot[shape=plaintext] head[label=head][shape=circle] 5[label=5]; 4[label=4]; 2[label=2]; 3[label=3]; 1[label=1]; less[label=less][shape=plaintext] greater[label=greater][shape=plaintext] none1[label=""][shape=plaintext] none2[label=""][shape=plaintext] subgraph cluster_0 { style=invis; { rank=1; head->none1; } } pivot -> 5; subgraph less { { rank=1; less->4->2->3->1; } } subgraph greater { { rank=1; greater->none2 } } } ``` ```c list_sort(&list_less); ``` ```graphviz digraph g{ node [shape=box]; pivot[shape=plaintext] head[label=head][shape=circle] 4[label=4]; 2[label=2]; 3[label=3]; 1[label=1]; less[label=less][shape=plaintext] greater[label=greater][shape=plaintext] none1[label=""][shape=plaintext] none2[label=""][shape=plaintext] subgraph cluster_0 { style=invis; { rank=same; head->4->2->3->1; } } pivot -> 4; subgraph less { { rank=same; less->none1; } } subgraph greater { { rank=same; greater->none2 } } } ``` ```graphviz digraph g{ node [shape=box]; pivot[shape=plaintext] head[label=head][shape=circle] 4[label=4]; 2[label=2]; 3[label=3]; 1[label=1]; less[label=less][shape=plaintext] greater[label=greater][shape=plaintext] none1[label=""][shape=plaintext] none2[label=""][shape=plaintext] subgraph cluster_0 { style=invis; { rank=1; head->none1; } } pivot -> 4; subgraph less { { rank=1; less->2->3->1; } } subgraph greater { { rank=1; greater->none2 } } } ``` ```graphviz digraph g{ node [shape=box]; pivot[shape=plaintext] head[label=head][shape=circle] 2[label=2]; 3[label=3]; 1[label=1]; less[label=less][shape=plaintext] greater[label=greater][shape=plaintext] none1[label=""][shape=plaintext] none2[label=""][shape=plaintext] subgraph cluster_0 { style=invis; { rank=1; head->none1; } } pivot -> 2; subgraph less { { rank=1; less->1; } } subgraph greater { { rank=1; greater->3 } } } ``` 以此類推最後透過 ```c list_add(&pivot->list, head); list_splice(&list_less, head); list_splice_tail(&list_greater, head); ``` 先把節點放進去,比節點大的放右邊,比節點小的放左邊,完成排序 ```graphviz digraph g{ node [shape=box]; head[label=head][shape=circle] 2[label=2]; 3[label=3]; 1[label=1]; none1[label=""][shape=plaintext] none2[label=""][shape=plaintext] subgraph cluster_0 { style=invis; { rank=same; head->1->2->3; } } } ``` ```graphviz digraph g{ node [shape=box]; head[label=head][shape=circle] 4[label=4]; 2[label=2]; 3[label=3]; 1[label=1]; none1[label=""][shape=plaintext] none2[label=""][shape=plaintext] subgraph cluster_0 { style=invis; { rank=same; head->1->2->3->4; } } } ``` ```graphviz digraph g{ node [shape=box]; head[label=head][shape=circle] 5[label=5]; 4[label=4]; 2[label=2]; 3[label=3]; 1[label=1]; none1[label=""][shape=plaintext] none2[label=""][shape=plaintext] subgraph cluster_0 { style=invis; { rank=same; head->1->2->3->4->5; } } } ``` --- ## 測驗二 **延伸問題** - [ ] 解釋程式碼運作原理,指出設計和實作的缺失 - [ ] 比較測驗 `1` 和測驗 `2` 的程式碼,設計實驗來分析二者效能,解釋為何非遞迴的實作未能總是比遞迴的實作快 - [ ] 提出效能改進方案,特別是避免依賴 `MAX_DEPTH` - [ ] 引入 [Introsort](https://en.wikipedia.org/wiki/Introsort), 也就是 quick sort 和 heap sort (或其他可避免前者 $O(n^{2})$ 最差時間複雜度的演算法)。加入 heapsort 的原因為,當 quicksort 迭代 $2 \times \log n$ 次後還排序完成,那就很可能會得到 $O(n^{2})$ 時間複雜度。此時切換為 heap sort 能將其時間複雜度控制在 $O(n \log n)$ 首先根據 [Optimized QuickSort](https://alienryderflex.com/quicksort/) 的所提及的方法與原版(這裡原版指的是網站指出的 wikipeida 版本)比較,包含了以下幾個優點 - 避免使用 function call ,減少 CPU 花在進入 function、離開 function 的時間 - 不使用 stack 來儲存資料,文中提及雖然 recursive 版本的 quick sort 看起來直觀簡單,但是會使用大量的 private stack 來儲存資料,會比起直接使用 `beg[]`、 `end[]` arrau 來的慢,且有機會造成 stack overflow,而文中提及的方法透過設定 `MAX_LEVELS` 來回傳 `NO` 來避免產生 failure - 在每一輪中,原版會 `swap` 非常多次,但文中版本僅 swap pivot 以及某個節點一次 - 其他像是多次移動同個物件、冗餘的移動、和自己進行 swap 都在文中新版本有所改善 測驗 2 給定具有 `MAX_DEPTH = 512` 的 stack 來模擬遞迴的過程且使用 `INIT_LIST_HEAD` 來初始化 `head` (此處上端為 `stack[0]` 依次往下遞增為 `stack[1], stack[2] ... stack[MAX_DEPTH-1]`) ```c struct list_head stack[MAX_DEPTH]; for (int i = 0; i < MAX_DEPTH; i++) INIT_LIST_HEAD(&stack[i]); ``` ```graphviz digraph list_head { rankdir=LR node[shape=record, style=bold]; stack [label="stack|{<prev1>prev|<next1>next}|{<prev2>prev|<next2>next}|...|{<prevn>prev|<nextn>next}"]; {rank=same; stack:prev1; stack:next1;} {rank=same; stack:prev2; stack:next2;} {rank=same; stack:prevn; stack:nextn;} stack:next1:e -> stack:prev1:w[arrowhead=normal, arrowtail=normal, dir=both]; stack:next2:e -> stack:prev2:w[arrowhead=normal, arrowtail=normal, dir=both]; stack:nextn:e -> stack:prevn:w[arrowhead=normal, arrowtail=normal, dir=both]; } ``` 把尚未排序好的 list 放進 stack 中 ```c int top = -1; list_splice_init(head, &stack[++top]); ``` ```graphviz digraph list_head { rankdir=LR node[shape=record, style=bold]; 4[label=4]; 5[label=5]; 6[label=6]; 2[label=2]; 8[label=8]; 3[label=3]; 1[label=1]; 7[label=7]; stack [label="stack|{<prev1>prev|<next1>next}|{<prev2>prev|<next2>next}|...|{<prevn>prev|<nextn>next}"]; {rank=same; stack:prev1; stack:next1;} {rank=same; stack:prev2; stack:next2;} {rank=same; stack:prevn; stack:nextn;} stack:next1:e ->4->5->3->6->2->8->1->7 stack:next2:e -> stack:prev2:w[arrowhead=normal, arrowtail=normal, dir=both]; stack:nextn:e -> stack:prevn:w[arrowhead=normal, arrowtail=normal, dir=both]; } ``` - 執行完上述程式碼時時 `top` 為 `0`,進入 `while` 迴圈 ```c INIT_LIST_HEAD(&partition); list_splice_init(&stack[top--], &partition); ``` ```graphviz digraph g{ node [shape=record]; rankdir=LR partition[label=partition][shape=plaintext] 4[label=4]; 5[label=5]; 6[label=6]; 2[label=2]; 8[label=8]; 3[label=3]; 1[label=1]; 7[label=7]; partition->4->5->6->2->8->3->1->7 } ``` - 執行完上述程式碼時時 `top` 為 `-1`,且符合第一個 `if` 條件,進入 `if` 迴圈 ```c struct list_head list_less, list_greater; INIT_LIST_HEAD(&list_less); INIT_LIST_HEAD(&list_greater); struct item *pivot = list_first_entry(&partition, struct item, list); list_del(&pivot->list); INIT_LIST_HEAD(&pivot->list); ``` ```graphviz digraph g{ rankdir=LR node [shape=box]; pivot[shape=plaintext] partition[label=partition][shape=plaintext] 4[label=4]; 5[label=5]; 6[label=6]; 2[label=2]; 8[label=8]; 3[label=3]; 1[label=1]; 7[label=7]; less[label=less][shape=plaintext] greater[label=greater][shape=plaintext] none1[label=""][shape=plaintext] none2[label=""][shape=plaintext] pivot->4 partition->5->6->2->8->3->1->7 less->none1 greater->none2 } ``` 首先把 `head` 當我 pivot ,從對 list 左邊開始找,找到小於 pivot 的值就會放入,(文中使用 `beg[]`, `end[]` 紀錄比較過程), linked list 則透過與測驗 1 一樣的 `list_less`、`list_greater` 來儲存資訊。 ```c GGGG (itm, is, &partition, list) { ... } ``` - 與測驗一相同, `GGGG` 為 `list_for_each_entry_safe` 將各個節點和 `pivot` 比大小並將對應結果分別放到 `list_less` 以及 `list_greater` ```c list_for_each_entry_safe(itm, is, &partition, list) { list_del(&itm->list); if (cmpint(&itm->i, &pivot->i) < 0) list_move(&itm->list, &list_less); else list_move(&itm->list, &list_greater); } ``` ```graphviz digraph g{ rankdir=LR node [shape=box]; pivot[shape=plaintext] partition[label=partition][shape=plaintext] 4[label=4]; 5[label=5]; 6[label=6]; 2[label=2]; 8[label=8]; 3[label=3]; 1[label=1]; 7[label=7]; less[label=less][shape=plaintext] greater[label=greater][shape=plaintext] none1[label=""][shape=plaintext] pivot->4 partition->none1 less->1->3->2 greater->7->8->6->5 } ``` 接著將 `pivot` 如同測驗 1 一樣在將 `less` 和 `greater` 合併時 `pivot` 會在中間,所以根據題目是將 `pivot` 放在 `less` 的某處,應為 `less` 的尾端 ```c HHHH (&pivot->list, &list_less); ``` - 故 `HHHH` 為 `list_move_tail` ```graphviz digraph g{ rankdir=LR node [shape=record]; 4[label=4]; 5[label=5]; 6[label=6]; 2[label=2]; 8[label=8]; 3[label=3]; 1[label=1]; 7[label=7]; less[label=less][shape=plaintext] greater[label=greater][shape=plaintext] less->1->3->2->4 greater->7->8->6->5 } ``` 再來就是和測驗 1 不同的部份,透過 `stack` 取代遞迴的過程,故將 `less` 以及 `greater` 放進去 `stack` (記得在此處 `top` 為 `-1`,為原本放未排序的 list 的地方,取出來做處理得時候有 -1),在推進去 `stack` 時為 `&stack[++top]` - 考試的時候寫成 `&stack[top++]`,不夠細心 ```c if (!list_empty(&list_greater)) list_splice_tail(&list_greater, IIII); if (!list_empty(&list_less)) list_splice_tail(&list_less, JJJJ); ``` - `IIII` = `JJJJ` = `&stack[++top]` ```graphviz digraph list_head { rankdir=LR node[shape=record, style=bold]; 4[label=4]; 5[label=5]; 6[label=6]; 2[label=2]; 8[label=8]; 3[label=3]; 1[label=1]; 7[label=7]; stack [label="stack|{<prev1>prev|<next1>next}|{<prev2>prev|<next2>next}|...|{<prevn>prev|<nextn>next}"]; {rank=same; stack:prev1; stack:next1;} {rank=same; stack:prev2; stack:next2;} {rank=same; stack:prevn; stack:nextn;} stack:next1:e ->7->8->6->5; stack:next2:e ->1->3->2->4; stack:nextn:e -> stack:prevn:w[arrowhead=normal, arrowtail=normal, dir=both]; } ``` 因為 `top` 值在推送進 stack 時有增加,所以 `while` 迴圈會持續運作,接著會對 `1 -> 3 -> 2 -> 5` 這個 list 做上述一樣的動作,持續到第一個 `if` 條件不滿足,也就是不滿足 ```c if (!list_empty(&partition) && !list_is_singular(&partition)) ``` - 代表從 `stack` 取出的節點為空或者是只有一個節點,此時進入 `else` 條件, 在第一次進入 `else` 前的 `stack` 樣子為 ```graphviz digraph list_head { rankdir=LR node[shape=record, style=bold]; 4[label=4]; 5[label=5]; 6[label=6]; 2[label=2]; 8[label=8]; 3[label=3]; 1[label=1]; 7[label=7]; partition[label=partition][shape=plaintext] stack [label="stack| {<prev1>prev|<next1>next}| {<prev2>prev|<next2>next}| {<prev3>prev|<next3>next}| {<prev4>prev|<next4>next}| {<prev5>prev|<next5>next}| ...| {<prevn>prev|<nextn>next}"]; stack:next1:e ->7->8->6->5; stack:next2:e ->3->2->4; partition->1; } ``` 偵測到 `stack` 的最頂端 (尚未從 `stack` 取出的圖中最下面),僅有一個節點 `1`,取出後 (此時的 `top` 會在指向 `3->2->4` 的位置,因為在切出 `partition` 的時候有做 `top--` 的動作)進入 `else` 條件首先要先 `top++` push `top` 一格 ```c top++; list_splice_tail(&partition, &stack[top]); while (top >= 0 && list_is_singular(&stack[top])) { struct item *tmp = list_first_entry(&stack[top], struct item, list); list_del(&tmp->list); INIT_LIST_HEAD(KKKK); list_add_tail(&tmp->list, head); } ``` 一進入後會將剛剛判斷為只有一個節點的 `partition` 放回 `stack[top]`,注意到題目中進行 `list_del(&tmp->list)` 且後面有 `list_add_tail(&tmp->list, head)`,代表已經將該節點放到 `head` 後面,以完成排序的部份,故 `KKKK` 必須對 `stack` pop 一格,`KKKK` = `&stack[top--]` 總結進入到 `else` 才會透過 `list_add_tail(&tmp->list, head);` 將排序好的節點放到 `head` 後面,且進入 `else` 的條件為此時 `stack[top]` 的位置僅有一個節點,且根據上述過程,`stack[top]` 必為未排序的 list 中最小的那個節點 (在推進去 `stack` 的時候是先推 `greater` 後再推 `less`,所以比較小的節點會在上面),以此依序將最小的節點接在 `head` 後面完成排序。 --- ## 測驗三 **延伸問題** - [ ] 解釋上述程式碼運作原理,指出其實作缺陷並改進 - [ ] 在上述 XOR linked list 的基礎,實作測驗 `1` 和 `2` 提及的快速排序演算法,注意要引入你改進效能的版本 - [ ] 修改 `xorlist_destroy`,使其不急著釋放記憶體,而搭配 [atexit(3)](https://man7.org/linux/man-pages/man3/atexit.3.html) 並在程式即將結束執行時,才批次處理資源釋放 在關鍵巨集中 ```c #define XOR_COMP(a, b) ((xor_node_t *) (LLLL)) ``` 根據函式名稱以及在程式內的用途知其為將 `a` 和 `b` 做 exclusive or 運算 - `LLLL` = `(uintptr_t)(a) ^ (uintptr_t)(b)` 另一個常用函式為 `address_of`,在透過 `assert` 確認 `n1`、`n2` 都為非 0 值後搭配 `XOR_COMP` 巨集進行 exclusive or 運算回傳下一個節點地址。 ```c static inline xor_node_t *address_of(xor_node_t *n1, xor_node_t *n2) { assert(n1 && n2); return XOR_COMP(n1, n2); } ``` XOR List 的迭代過程透過前一個的地址以及這一格的 `link` 進行 `xor` 運算來獲得下一格的位址,也就是說當前位置的 `link` 可以透過前後兩個點的地址進行 `xor` 運算得到 根據上述,程式內也定義了 `xor_list_t` 來代表 list 所需的資訊 ```c typedef struct _xor_list_struct { xor_node_t head, tail; uint32_t count; } xor_list_t; ``` - 根據前面敘述,要拿到當前節點的 `link` ,需要前後兩個點的地址,所以在這裡也直接定義了 `head` 以及 `tail` 代表 `list` 的頭尾 ```graphviz digraph list { rankdir=LR; node[shape=record, style=bold]; subgraph cluster_0 { node [shape=record]; head [label="{head}"]; tail [label="{tail}"]; count [label="count"][shape=plaintext]; style=bold; label=list } } ``` 查看 `xorlist_for_each` 巨集 ```c #define xorlist_for_each(node, rp, rn, list) \ for (rp = &(list)->head, node = rp->cmp; node != &(list)->tail; \ rn = address_of(rp, node->cmp), rp = node, node = rn) ``` - 可以看到在迭代的過程中 `rn` 為下一個節點的地址,是透過 `rp` (前一個節點的地址) 和 `node->cmp` (當前節點的 link) 做 `xor` 運算得到。 在 `main` 中,先將 0~100 的值透過 `xorlist_add` 加入到 `list` 中, ```c xornode_init(&new->xornode); new->value = i; xorlist_add(&list, &new->xornode); ``` ```graphviz digraph list { rankdir=LR; node[shape=record, style=bold]; null[shape=plaintext] subgraph cluster_0 { node [shape=record]; head [label="{head}"]; tail [label="{tail}"]; count [label="count"][shape=plaintext]; style=bold; label=list } subgraph cluster_1 { label="new node 1" value[label=value] xornode[label="xornode|{<cmp>cmp}" ] } head->xornode:cmp xornode:cmp->null } ``` ```graphviz digraph list { rankdir=LR; node[shape=record, style=bold]; null[label=null][shape=plaintext] subgraph cluster_0 { node [shape=record]; head [label="{head}"]; tail [label="{tail}"]; count [label="count"][shape=plaintext]; style=bold; label=list } subgraph cluster_1 { label="new node 1" value[label=value] xornode[label="<label>xornode|{<cmp>cmp}" ] } subgraph cluster_2 { label="new node 2" value2[label=value] xornode2[label="<label>xornode|{<cmp>cmp}" ] } head->xornode:label xornode:cmp->xornode2:label xornode2:cmp->null } ``` 這裡的 `cmp` 若是要取得下一個節點的地址則需要與前一個節點地址做 `xor` 考慮題目及,題目給的參考執行輸出, ```c int xorlist_add(xor_list_t *list, xor_node_t *n) { xor_node_t *real_next; if (!n) return ENOMEM; xor_node_t *real_prev = &list->head; xor_node_t *node = real_prev->cmp; if (node == &list->tail) real_next = MMMM; else real_next = node; real_prev->cmp = n; n->cmp = XOR_COMP(real_prev, real_next); real_next->cmp = XOR_COMP(n, XOR_COMP(real_prev, PPPP)); list->count++; return 0; } ``` - 題目 `MMMM` 用以處理初始的條件,在進入 `xorlist_add` 之前已經呼叫過 `XORLIST_INIT(h)` 巨集,將 `head` 以及 `tail` 的 `cmp` 互相設定為對方,在初始條件下 `real_next` 即為 `list_tail`,故 `MMMM` = `&list->tail`,此時這個 `if-else` 條件似乎顯得沒必要? 可直接使用 `real_next` = `node` 做取代 (待驗證) 考慮到若 `if` 條件滿足,則代表 `node = real_prev->cmp` 為 `&list->tail`,此時 `node` 等價於 `&list_tail` ,因為是將新的節點 `n` 插入至 list 的第一個節點,且 `node` 指向 `real_prev` 的壓縮地址 (`cmp`),因為是 `head` 的關係所以壓縮地址必為下一個節點地址,同時 `node` 也會是,故直接省略 `if` 條件也可以正常執行 ```diff int xorlist_add(xor_list_t *list, xor_node_t *n) { xor_node_t *real_next; if (!n) return ENOMEM; xor_node_t *real_prev = &list->head; xor_node_t *node = real_prev->cmp; - if (node == &list->tail) - real_next = MMMM; - else - real_next = node; + real_next = node; real_prev->cmp = n; n->cmp = XOR_COMP(real_prev, real_next); real_next->cmp = XOR_COMP(n, XOR_COMP(real_prev, PPPP)); list->count++; return 0; } ``` 根據題目顯示,`list_add` 是新增節點在 list 的頭 進入 `list_add` 初始狀態如圖 `A`、`B` 為該節點地址 ```graphviz digraph list { rankdir=LR; node[shape=record, style=bold]; head[label="A|{<cmp>cmp=B}"]; tail[label="B|{<cmp>cmp=A}"]; head:cmp->tail:cmp[dir=both] } ``` 在初始狀態新增一個節點地址為 `C` 後的結果應為 ```graphviz digraph list { rankdir=LR; node[shape=record, style=bold]; head[label="A|{<cmp>cmp=B}"]; tail[label="B|{<cmp>cmp=C}"]; node1[label="C|{<cmp>cmp=A \oplus B}"] n[label="n"][shape=plaintext] rn[label="real_next"][shape=plaintext] rp[label="real_prev"][shape=plaintext] head:cmp->node1:cmp[dir=both] node1:cmp->tail:cmp[dir=both] n->node1 rn->tail rp->head } ``` - 而 `PPPP` 用以更新 `real_next->cmp` 的值,從 xor-list 的方法知道該節點的 `cmp` 為前後節點地址做 `xor` 運算 ```c real_next->cmp = XOR_COMP(n, XOR_COMP(real_prev, PPPP)); ``` 此時要讓 `real_next->cmp` 從 A 更新為 C,進行運算 $C \oplus (A\oplus PPPP)$,此時 `PPPP` 應為 A ,用以消除 A (因為 $A\oplus A = 0$),故 `PPPP` 為 `real_next->cmp`