---
tags: sysprog21
---
# 2021q1 Homework1 (quiz1)
contributed by < `akamayu-ouo` >
> [第一周測驗題](https://hackmd.io/@sysprog/linux2021-quiz1)
```cpp
#define LLL left = &((*left)->next)
#define AAA &right
#define BBB &left
#define CCC \
list_concat(&result, pivot); \
list_concat(&result, right)
```
### list_concat
```cpp=
static inline void list_concat(node_t **left, node_t *right) {
while (*left)
left = &((*left)->next);
*left = right;
}
```
假設這個函式被用以下方式呼叫
```cpp
list_concat(&X, Y);
```
因此 `left` 的初始值為 `&X`,`right` 則為 `Y` 。
#### X 為 NULL
此時的關係
```graphviz
digraph {
rankdir=LR;
edge[arrowsize=0.7]
node[shape=oval];
r [label="right"]
l [label="left"]
x [label="X=NULL"]
node[shape=record];
a [label="{A|val|<next>}"]
b [label="{B|val|<next>}"]
dotdot [label="……",shape=none]
a:next:c -> b
b:next:c -> dotdot
l:c -> x
r:c -> a
}
```
跳過迴圈後執行 `*left = right;` ,將 `*left`(`X`)設成 `right` (`A` 的記憶體位置)。
```graphviz
digraph {
rankdir=LR;
edge[arrowsize=0.7]
node[shape=oval];
r [label="right"]
l [label="left"]
x [label="X"]
node[shape=record];
a [label="{<struct> A|val|<next>}"]
b [label="{B|val|<next>}"]
dotdot [label="……",shape=none]
a:next:c -> b
b:next:c -> dotdot
l:c -> x
r:c -> a
x -> a:struct:n [weight=0]
{rank=same;l r}
}
```
回傳後的結果即為
```graphviz
digraph {
rankdir=LR;
edge[arrowsize=0.7]
node[shape=oval];
x [label="X"]
node[shape=record];
a [label="{A|val|<next>}"]
b [label="{B|val|<next>}"]
dotdot [label="……",shape=none]
a:next:c -> b
b:next:c -> dotdot
x -> a [weight=0]
}
```
#### X 不為 NULL
進入函式時變數的關係如下:
```graphviz
digraph {
rankdir=LR;
edge[arrowsize=0.7]
node[shape=oval];
r [label="right"]
l [label="left"]
x [label="X"]
node[shape=record];
a [label="{A|val|<next>}"]
b [label="{B|val|<next>NULL}"]
c [label="{C|val|<next>}"]
d [label="{D|val|<next>}"]
dotdot [label="……",shape=none]
a:next:c -> b
c:next:c -> d
d:next:c -> dotdot
l:c -> x
r:c -> c
x:c -> a
{rank=same; r x}
}
```
因為對指標 `A->B` 等價於 `(*A).B` ,第 3 行可以改寫成
```c
left = &((**left).next);
```
將 `*left` 、`**left` 以及 `(**left).next` 分別標注在圖上就是
```graphviz
digraph {
rankdir=LR;
edge[arrowsize=0.7]
node[shape=oval];
l [label="left"]
x [label="X"]
node[shape=record];
a [label="{A|val|<next> (**left).next}"]
b [label="{B|val|<next>NULL}"]
a:next:c -> b
l:c -> x
x:c -> a
subgraph cluster1{x;label="*left";}
subgraph cluster2{a;label="**left";}
}
```
更新 `left` 後它會指向 `A.next`,此時 `**left` 即為 `A` 的下一個元素 `B` 。值得注意的是 `X` 的值維持不變,呼叫者仍有辦法以此找到 linked list 的起始點 `A`(然而在函數中已無法得知 `X` 的值了)。
```graphviz
digraph {
rankdir=LR;
edge[arrowsize=0.7]
node[shape=oval];
l [label="left"]
x [label="X",color=grey60,fontcolor=grey60]
node[shape=record];
a [label="{A|val|<next>*left}"]
b [label="{B|val|<next>NULL}"]
a:next:c -> b
l -> a:next:n [weight=0]
x -> a [color=grey60]
subgraph cluster1{b;label="**left";}
}
```
重複 `left = &((**left).next);` ,直到 `*left` 的值是 NULL 。此時 `left` 指向以 `X` 為首的 linked list 中最後一個元素的 `next` 變數。
```graphviz
digraph {
rankdir=LR;
edge[arrowsize=0.7]
node[shape=oval];
l [label="left"]
x [label="X",color=grey60,fontcolor=grey60]
node[shape=record];
a [label="{A|val|<next>}"]
b [label="{B|val|<next>*left=NULL}"]
a:next:c -> b
l:e -> b:next:n [weight=0]
x -> a [color=grey60]
}
```
跳出迴圈後執行 `*left = right;` ,將 `B.next` 設成 `right` 。
```graphviz
digraph {
rankdir=LR;
edge[arrowsize=0.7]
node[shape=oval];
x [label="X",color=grey60,fontcolor=grey60]
r [label="right"]
l [label="left"]
node[shape=record];
c [label="{<struct> C |val|<next>}"]
d [label="{D|val|<next>}"]
a [label="{A|val|<next>}"]
b [label="{B|val|<next>*left}"]
dotdot [label="……",shape=none]
a:next:c -> b
c:next:c -> d
d:next:c -> dotdot
b:next:s -> c:struct:n [weight=0]
l:e -> b:next:n[weight=0]
r -> c
x -> a [color=grey60]
{rank=same;a c}
{rank=same;x r l}
}
```
回傳後呼叫者看到的結果如下
```graphviz
digraph {
rankdir=LR;
edge[arrowsize=0.7]
node[shape=oval];
x [label="X"]
node[shape=record];
c [label="{<struct> C |val|<next>}"]
d [label="{D|val|<next>}"]
a [label="{A|val|<next>}"]
b [label="{B|val|<next>}"]
dotdot [label="……",shape=none]
a:next:c -> b
c:next:c -> d
d:next:c -> dotdot
b:next:c -> c
x -> a
}
```
### Quick Sort
```cpp
void quicksort(node_t **list)
{
if (!*list)
return;
/* 選擇 pivot:從要排序的數字中選擇一個數字,此處直接使用第一個數字 */
node_t *pivot = *list;
int value = pivot->value;
node_t *p = pivot->next;
pivot->next = NULL;
/* 分割:將要排序的數字依照跟 pivot 的大小關係分為兩組( pivot 不參與分組 )*/
node_t *left = NULL, *right = NULL;
while (p) {
node_t *n = p;
p = p->next;
list_add_node_t(n->value > value ? &right : &left, n);
}
/* 遞迴:用 quick sort 排好那兩組數字 */
quicksort(&left);
quicksort(&right);
/* 組合:以「不大於 — pivot — 大於」的順序組合在一起,得到排好的序列 */
node_t *result = NULL;
list_concat(&result, left);
list_concat(&result, pivot);
list_concat(&result, right);
*list = result;
}
```
用圖來解釋
- 選擇 pivot
```graphviz
digraph {
4 [shape=legend];
pivot;
pivot->4;
{rank=same; pivot 4}
rankdir=LR;
node[shape=legend]
4->7->3->8->2->6->9->1
}
```
- 分割成兩條 lists
```graphviz
digraph {
rankdir=LR;
node [shape=oval]
left right pivot
node[shape=legend]
pivot->4;
left->3->2->1
right->7->8->6->9
}
```
- 將其分別排序
```graphviz
digraph {
rankdir=LR;
node [shape=oval]
left right pivot
node[shape=legend]
pivot->4
left->1->2->3
right->6->7->8->9
}
```
- 重新接合
```graphviz
digraph {
rankdir=LR;
node [shape=oval]
node[shape=legend]
1->2->3->4->6->7->8->9
}
```
### Memory Leak
範例測試程式中,若檢查發現 linked list 沒有正確排序,程式會馬上結束,但沒有釋放記憶體,造成 memory leak。
```cpp
if (!list_is_ordered(list))
return EXIT_FAILURE;
list_free(&list);
return EXIT_SUCCESS;
```
將範例程式中的 `quicksort(&list)` 那行註解掉後使用 `valgrind --leak-check=full` 測試的結果如下:
```shell
==3686237== HEAP SUMMARY:
==3686237== in use at exit: 320 bytes in 20 blocks
==3686237== total heap usage: 21 allocs, 1 frees, 1,344 bytes allocated
==3686237==
==3686237== 320 (16 direct, 304 indirect) bytes in 1 blocks are definitely lost in loss record 2 of 2
==3686237== at 0x483E77F: malloc (vg_replace_malloc.c:307)
==3686237== by 0x1091A1: list_make_node_t (in /.../sysprog21/Week1/quiz1/main)
==3686237== by 0x1094BD: main (in /.../sysprog21/Week1/quiz1/main)
==3686237==
==3686237== LEAK SUMMARY:
==3686237== definitely lost: 16 bytes in 1 blocks
==3686237== indirectly lost: 304 bytes in 19 blocks
==3686237== possibly lost: 0 bytes in 0 blocks
==3686237== still reachable: 0 bytes in 0 blocks
==3686237== suppressed: 0 bytes in 0 blocks
==3686237==
==3686237== For lists of detected and suppressed errors, rerun with: -s
==3686237== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
```
從輸出資訊(第 8 與 9 行)可以看出沒有被收回的記憶體是由函式 `list_make_node_t` 透過 `malloc` 配置。
我將測試程式修改如下以避免 memory leak。
```diff
- if (!list_is_ordered(list))
- return EXIT_FAILURE;
+ const bool ordered = list_is_ordered(list);
list_free(&list);
- return EXIT_SUCCESS;
+
+ return ordered ? EXIT_SUCCESS : EXIT_FAILURE;
```
### Random
根據 [rand(3)](https://linux.die.net/man/3/rand):
> If no seed value is provided, the rand() function is automatically seeded with a value of 1.
也就是說,若在呼叫 `rand()` 之前沒有用 `srand()` 設定過種子, `rand()` 的行為會跟使用 `1` 作為種子一樣。因此範例程式才會總是產生出一樣的測試資料。
我改用 [lrand48](https://linux.die.net/man/3/lrand48) 來產生亂數。
> All the functions work by generating a sequence of 48-bit integers, Xi, according to the linear congruential formula:
`Xn+1 = (aXn + c) mod m, where n >= 0`
>The parameter m = 2^48, hence 48-bit integer arithmetic is performed. Unless lcong48() is called, a and c are given by:
>`a = 0x5DEECE66D`
>`c = 0xB`
To Be Continued ...