# 2024q1 Homework1 (lab0)
contributed by < [ollieni](https://github.com/ollieni) >
:::danger
詳閱作業說明,指定的格式中有空白字元,留意各式細節,唯有重視小處並步步為營,方可挑戰原始程式碼規模超越三千七百萬行的 Linux 核心
:::
## 開發環境
```shell
$ gcc --version
gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0
Copyright (C) 2019 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
Byte Order: Little Endian
Address sizes: 39 bits physical, 48 bits virtual
CPU(s): 4
On-line CPU(s) list: 0-3
Thread(s) per core: 1
Core(s) per socket: 4
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 154
Model name: 12th Gen Intel(R) Core(TM) i7-12700H
Stepping: 3
CPU MHz: 2688.004
BogoMIPS: 5376.00
Hypervisor vendor: KVM
Virtualization type: full
L1d cache: 192 KiB
L1i cache: 128 KiB
L2 cache: 5 MiB
L3 cache: 96 MiB
NUMA node0 CPU(s): 0-3
```
## 指定的佇列操作
> 修改 queue.c ,使其滿足 `$ make test` 自動評分系統的所有項目
### q_new
> [commit 55648cf](https://github.com/ollieni/lab0-c/commit/55648cf5f3075b37eefa954e15bd2a1f7f952b1e)
建立一個空的佇列,並將其下一個和上一個的指標都指向自己。
:::danger
避免非必要的項目縮排 (即 `* `),用清晰、明確,且流暢的漢語書寫。
:::
宣告一個 `list_head` 型別的指標並使用 `malloc` <s>分配</s> 配置記憶體空間再2. 用 `list.h` 中的 `INIT_LIST_HEAD` 將其指向下一個和上一個的指標都指向自己。
:::danger
allocate 的翻譯是「配置」,以區隔 dispatch (分配/分發) 的翻譯。
:::
### q_free
> [commit 08319cb](https://github.com/ollieni/lab0-c/commit/08319cbc0cf23f5c7542ecbf0a1da3d990cd6f07)
將佇列所使用的全部記憶體釋放,若是佇列是空的則不動作。
:::danger
留意[資訊科技詞彙翻譯](https://hackmd.io/@sysprog/it-vocabulary)
:::
使用 `list.h` 中的 `list_for_each_safe` 去<s>遍歷</s> 走訪整個佇列
將走過的節點用 `list_del` 從佇列中移去
使用 `list_entry` 去取得該節點 container (也就是 element_t)位置,把 value 和該節點之記憶體釋放。
:::danger
避免非必要的項目縮排 (即 `* ` 和 `- `),以清晰、明確,且流暢的漢語書寫。
:::
### `q_insert_head`, `q_insert_tail`
>[commit 303414a](https://github.com/ollieni/lab0-c/commit/303414acf568823eff8243d3e09c7ecedd691d76)
插入一個節點到佇列的頭(尾)。
<s>分配</s> 記憶體空間給新結點
將要插入的字串存到 value
使用 `list.h` 中的 `list_add` / `list_add_tail` 將節點插入佇列中。
:::danger
避免非必要的項目縮排 (即 `* ` 和 `- `),以清晰、明確,且流暢的漢語書寫。
:::
一開始在寫完後執行 `make check` 產生以下錯誤訊息。
```
# Fill it with some values. First at the head
ERROR: Need to allocate and copy string for new queue element
l = [dolphin]
```
根據錯誤訊息可得知,需要先使用 `malloc` 配置空間給新的元素,並複製要插入的字串。
```diff
bool q_insert_head(struct list_head *head, char *s)
{
element_t *new = malloc(sizeof(element_t *));
+ new->value = malloc(sizeof(s)+1);
+ strcpy(new->value, s);
- new->value = s;
list_add(&new->list, head);
return true;
}
```
修改程式後可以順利執行,但是當 commit 的時候被偵測到有危險的函式。
```
Dangerous function detected in /home/ollieni/linux2024/lab0-c/queue.c
31: strcpy(new->value, s);
41: strcpy(new->value, s);
Read 'Common vulnerabilities guide for C programmers' carefully.
https://security.web.cern.ch/security/recommendations/en/codetools/c.shtml
```
在查看錯誤訊息提供的 [Common vulnerabilities guide for C programmers](https://security.web.cern.ch/recommendations/en/codetools/c.shtml)之後,得到了以下的資訊 :
"The strcpy built-in function does not check buffer lengths and may very well overwrite memory zone contiguous to the intended destination. In fact, the whole family of functions is similarly vulnerable: strcpy, strcat and strcmp."
這些文字的大意是 :
strcpy 內建函式並**未檢查緩衝區的長度,可能會覆寫意圖目的地相鄰的記憶體區域**。事實上,整個函式家族都存在相似的風險:strcpy、strcat 和 strcmp。
我根據網頁中的推薦使用 `strncpy` 。
```diff
bool q_insert_head(struct list_head *head, char *s)
{
+ int len = strlen(s);
+ strncpy(new->value, s, len + 1);
- strcpy(new->value, s);
}
```
### `q_remove_head`, `q_remove_tail`
> [commit 4b5aafe](https://github.com/ollieni/lab0-c/commit/4b5aafee76e84c2d5fc570aafdd628872a60e32c)
將佇列中第一個(最後一個)節點移去。
使用 `list_entry` 將第一個(最後一個)節點之 container 找出。
將節點之 value 複製存於 sp ,使用 `list_del`將節點移去。
:::danger
避免非必要的項目縮排 (即 `* ` 和 `- `),以清晰、明確,且流暢的漢語書寫。
:::
### q_size
>[commit 51235c1](https://github.com/sysprog21/lab0-c/commit/51235c10591806f7ee2c3ca71c844b3ef6628bb9)
計算佇列的長度。
用教材中之程式。
### q_delete_mid
>[commit e636266](https://github.com/ollieni/lab0-c/commit/e636266d0de819382289131217eb4462d94f931d)
刪除佇列中間的節點。
使用 `q_size` 得到佇列之長度後。
用 while 迴圈找到中間的節點,再將其刪除。
:::danger
避免非必要的項目縮排 (即 `* ` 和 `- `),以清晰、明確,且流暢的漢語書寫。
:::
### q_delete_dup
>[commit 1f9b9be](https://github.com/ollieni/lab0-c/commit/1f9b9be22594aa1ce57f2fa4e34c02c2c4a4f563)
將佇列中重複的元素全部刪除。
將節點和下一個節點之 value 進行比較,若是相同就刪除當下節點,並用 count++ 記錄此 value 有重複 (在 C 語言中除了 0 都是 True)。
在不相等時,去查看 count 來判斷是否刪除當下節點。
在最後一個節點時,也需查看 count 來判斷是否刪除當下節點。
:::danger
避免非必要的項目縮排 (即 `* ` 和 `- `),以清晰、明確,且流暢的漢語書寫。
:::
一開始的實作會如同這題 [leetcode](https://leetcode.com/problems/remove-duplicates-from-sorted-list/description/),無法將重複的全部刪除。
`ERROR: Duplicate strings are in queue or distinct strings are not in queue`
後來使用 count 記錄當下節點之前是否重複,在我自己的測試中能得到正確的結果。
但是在 make test 中出現問題,在自己排查後,發現是最後一個節點永遠不會被刪除。
再多加上一個判斷就解決了。
### q_swap
>[commit cd02814](https://github.com/ollieni/lab0-c/commit/cd02814d591bc2c09a351b573f16cd0aa365d179)
將佇列中每兩個相鄰元素的值進行交換。
1. 初始化兩個指標 cur 和 prev 分別指向連結串列中的第二個和第三個元素。
2. 使用 q_size 函數計算連結串列的大小,存儲在 size 中。
3. 進入一個迴圈,當 i 小於等於 size - 2 時,執行以下步驟:
透過 list_entry 宏將 cur 和 prev 轉換為 element_t 結構的指標,分別為 cur_element 和 prev_element。
交換 cur_element 和 prev_element 中的 value 成員。
更新 cur 和 prev 指標,使其指向下一對元素。
增加 i 的值,以繼續處理下一對元素。
:::danger
善用 List API,撰寫出更精簡的程式碼。
:::
### q_reverse
>[commit fa5829a](https://github.com/ollieni/lab0-c/commit/fa5829a01bd919cb2259e23c30d5233c7669c572)
反轉整個佇列。
1. 宣告三個指標變數 pos、temp 和 new,用於走訪連結串列。
2. 使用 list_for_each_safe 走訪連結串列,對每個元素執行以下操作:
將 pos->next 指向 pos->prev,將 pos->prev 指向 new。
3. 最後將 head 自身的 next 和 prev 互換。
### q_reverseK
>[commit da08d7c](https://github.com/ollieni/lab0-c/commit/da08d7c3d352a63be5507f2199dbb0f6d6d21f0f)
按照指定的 k 大小,先將整個串列分成 k 組,再對其反轉。
將 current 指標向前移動 k 次,切斷 head 和 current->prev 之間的元素,存在 temp 中。
呼叫先前定義的 q_reverse 函數對 temp 進行反轉。
使用 list_splice_tail_init 將反轉後的 temp 串列添加到 result 串列的尾部。
使用 list_splice_init 將最終結果 result 串列合併到原始 head 串列。
:::danger
改進你的漢語表達。
:::
### q_sort
>[commit ed69f07](https://github.com/ollieni/lab0-c/commit/ed69f07cab5777bb55a784266850d76c292d3ea6)
使用 merge sort 實作,將佇列中元素依照輸入的 descend 參數決定要輸出升序或降序的結果。
我分成 4 個函式實作 :
merge 函式:
此函式用於合併兩個已經排序的佇列 l1 和 l2。
創建三個指標 cur1、cur2 和 swap 以<s>遍歷</s> ??? l1 和 l2,以及存儲合併結果的 result 佇列。
在迴圈中,比較 cur1 和 cur2 指向的元素,將較小的元素添加到 result 佇列中。
最後,處理剩餘的元素,並將 result 佇列接合到原始 l1 或 l2 中。
返回合併後的佇列。
split_two 函式:
此函式用於將佇列分成兩半,返回分割點的位置。
sort 函式:
此函式實現合併排序,接受一個指向佇列 head 和一個布林值 descend(用於指定升序或降序排列)作為參數。
使用 split_two 函式將佇列分為兩半,然後分別對兩半進行遞迴排序。
使用 merge 函式將兩半合併為一個排序好的佇列。
返回排序後的佇列。
q_sort 函式:
:::danger
call 的翻譯是「呼叫」,這個函式的權限沒大到可以「調用」你。
:::
此函式用於<s>調用</s>呼叫 sort 函式進行合併排序。
先檢查連結串列的大小,如果為空或只有一個元素,則直接返回。
將連結串列的頭節點刪除,將剩餘部分進行排序,最後將排序後的結果重新連結到原始的 head。
### q_ascend
>[commit cf8cdde](https://github.com/ollieni/lab0-c/commit/cf8cddee562f741a89402e0472d4c5f00a53a747)
刪除具有較小值在右側的節點。
1. 使用 list_entry 取得 head 節點的前一個節點的 value 值,將其初始化為 right_max。
2. 使用迴圈從 head 節點的前一個節點(尾端)往前,走訪整個連結串列,對每個節點進行以下操作:
透過 list_entry 取得節點的 element_t 結構指標 entry。
如果 right_max 與 entry->value 相等或更大,更新 right_max。
否則,刪除當前節點,釋放相應的資源(value 和 entry)。
### q_descend
>[commit cf8cdde](https://github.com/ollieni/lab0-c/commit/cf8cddee562f741a89402e0472d4c5f00a53a747)
刪除具有較大值在右側的節點。
實作和ascend一樣,只要將判斷式中的 `>=` 改成 `<=` 即可。
### q_merge
>[commit 19d7e81](https://github.com/ollieni/lab0-c/commit/19d7e81febf9125fe0db5309734746760f16db31)
合併多個排好序的佇列,並將結果放到第一個佇列裡。
以下方兩個函式實作:
:::danger
對應到數學公式,function 應翻譯為「函數」,後者就像是台「機器」,它能夠將集合 A 裡面的每個元素「唯一地」對應到集合 B 裡的一個元素。但在 C 語言一類的通用程式語言中,function 未必具備數學函數的意涵,也就是缺乏「唯一對應」的特性,例如對於 [time](https://man7.org/linux/man-pages/man2/time.2.html) 來說,給定同樣的輸入,卻會在不同時間點得到不同的結果,在 C 語言一類的通用程式語言中,甚至可省略傳回值,後者就不符合「函數」的行為。因此,當在通用的程式設計中,function 應翻譯為「函式」,表示「衍生自數學函數的常式 (routine)」。另外,對於更一般的描述,function 會指「功能」一類的意涵,應合適挑選譯詞。
$\to$ [資訊科技詞彙翻譯](https://hackmd.io/@sysprog/it-vocabulary)
:::
merge_two_list 函式:
此函式用於合併兩個已經排序的連結串列 l1 和 l2。
創建三個指標 cur1、cur2 和 swap 以遍歷 l1 和 l2,以及儲存合併結果的 result 串列。
在迴圈中,比較 cur1 和 cur2 指向的元素,將較小(或相等)的元素添加到 result 串列中。
將剩餘的元素依次添加到 result 串列中。
最後,將 result 串列接合到原始 l1 中。
q_merge 函式:
此函式的目的是合併多個已排序佇列,形成一個新的排序佇列。
使用 merge_two_list 函數將每個佇列逐一合併到第一個串列 context1->q 中。
如果指定的排序方式為降序,則呼叫 q_reverse 函數對最終合併的佇列進行反轉。
返回最終合併的佇列的大小,即呼叫 q_size 函數。
### make test 結果
做完所有項目後,我 make test 有時候會得到100分,有時候不會。
<s>
![messageImage_1709477296807](https://hackmd.io/_uploads/B1QKk1mTT.jpg)
</s>
:::danger
文字訊息避免用圖片來表示,否則不好搜尋和分類
:::
```shell
--- trace-14-perf 6/6
+++ TESTING trace trace-15-perf:
# Test performance of sort with random and descending orders
# 10000: all correct sorting algorithms are expected pass
# 50000: sorting algorithms with O(n^2) time complexity are expected failed
# 100000: sorting algorithms with O(nlogn) time complexity are expected pass
--- trace-15-perf 6/6
+++ TESTING trace trace-16-perf:
# Test performance of insert_tail
--- trace-16-perf 6/6
+++ TESTING trace trace-17-complexity:
# Test if time complexity of q_insert_tail, q_insert_head, q_remove_tail, and q_remove_head is constant
Probably constant time
Probably constant time
Probably constant time
Probably constant time
--- trace-17-complexity 5/5
--- TOTAL 100/100
ollieni@ollie-CA:~/linux2024/lab0-c$
```
但 push 上 github ,無法通過 CI/CD的測試。
經過推測,發現有可能是老師在作業文件中提到的問題。
**注意:現有實作存在若干致命缺陷,請討論並提出解決方案**
## 確保 qtest 提供的 web 命令得以搭配上述佇列實作使用
嘗試使用 `web` 去確認我實作的程式是否能搭配,
測試指令 :
```shell
$ curl http://localhost:9999/new
$ curl http://localhost:9999/ih/1
$ curl http://localhost:9999/ih/2
$ curl http://localhost:9999/ih/3
$ curl http://localhost:9999/sort
$ curl http://localhost:9999/quit
```
以下是測試結果 :
```shell
$ ./qtest
cmd> web
listen on port 9999, fd is 3
cmd> l = []
cmd> l = [1]
cmd> l = [2 1]
cmd> l = [3 2 1]
cmd> l = [1 2 3]
cmd> Freeing queue
```
## 在 qtest 新增 shuffle 命令
<s>指令</s>
:::danger
command 是「命令」,而非「指令」(instruction)
:::
根據教學文件[ qtest 命令直譯器的實作](https://hackmd.io/@sysprog/linux2024-lab0-b#qtest-%E5%91%BD%E4%BB%A4%E7%9B%B4%E8%AD%AF%E5%99%A8%E7%9A%84%E5%AF%A6%E4%BD%9C),在 console_init() 中加上 `ADD_COMMAND(shuffle, "Shuffle the nodes of the queue", "");`,並寫一個 do_shuffle 去呼叫我們實作的q_shuffle。
### q_shuffle
雖然教材中有,但我還是嘗試寫了一個版本。
1. 獲取連結串列的大小,存儲在變數 length 中,使用 q_size 函數。
:::danger
algorithm 是「演算法」,而「算法」是 the way to calculate,一如你以前國小考試作答的計算過程。
:::
2. 使用 Fisher-Yates shuffle 演算法 <s>算法</s>:
進行一個迴圈,從最後一個元素開始,依次對每個位置進行處理。
在每次迭代中,生成一個隨機數 j,範圍為 [0, i],其中 i 為當前迭代的位置。
透過兩個迴圈分別找到第 i 和 j 位置對應的節點。
交換這兩個節點的 value 值。
以下為執行測試程式後的結果 :
```
$ python3 scripts/shuffle.py
Expectation: 41666
Observation: {'1234': 41656, '1243': 41850, '1324': 41965, '1342': 41372, '1423': 41682, '1432': 41783, '2134': 41375, '2143': 41391, '2314': 41578, '2341': 41945, '2413': 41157, '2431': 41731, '3124': 41651, '3142': 41445, '3214': 41810, '3241': 41649, '3412': 41421, '3421': 41752, '4123': 42126, '4132': 41662, '4213': 41641, '4231': 42100, '4312': 41487, '4321': 41771}
chi square sum: 31.539144626314016
```
## 啟發和疑惑
### ClangFormat
整個專案的程式風格統一,才能有利於協做並更好的維護程式。
Clang Format 這個工具,可以很有效率的幫助我們輕鬆統一程式風格。
我們只要在專案的根目錄下,寫一個 `.clang-format` 檔案,裡面可以詳細定義要遵循的程式風格,比如說 : 不能使用 `tab` 、使用 4 個空白代替,我們在下指令的時候,沒有指定 `-style`,clang format 預設會去抓我們定義的 `.clang-format` 檔案。
### Git Hooks
:::danger
你查詢哪些材料?該列出來,並以第一手材料為主。
:::
<s>根據我查詢的資料</s> 根據 Git 官方手冊 [Git Hooks](https://git-scm.com/book/zh-tw/v2/Customizing-Git-Git-Hooks) , Git Hook 這個功能的是將一些簡單的 CI 從 server 端,移到開發人員端做,我們在 commit 前就能確保我們程式符合規範。
這些規範的腳本會在 `.git/hooks/` 這個目錄下,有些腳本有 `.sample` 這個副檔名,代表沒啟用,如果要啟用只需將 `.sample` 刪掉即可。
在 Git 官方手冊中,有提到我們使用的 pre-commit 是 client-side hook,這種 hook 不會隨著 git clone 被複製,我就有疑問我們沒有特別設定,怎麼有這個功能。
在仔細重看了一次 lab0 文件後,找到在 [Git Hooks 進行自動程式碼排版檢查](https://hackmd.io/@sysprog/linux2024-lab0/%2F%40sysprog%2Flinux2024-lab0-a#Git-Hooks-%E9%80%B2%E8%A1%8C%E8%87%AA%E5%8B%95%E7%A8%8B%E5%BC%8F%E7%A2%BC%E6%8E%92%E7%89%88%E6%AA%A2%E6%9F%A5),有寫到 "首次執行 make 後,Git pre-commit / pre-push hook 將自動安裝到現行的工作區 (workspace)",於是我去看 Makefile,注意到有執行一個腳本 `$(GIT_HOOKS):
@scripts/install-git-hooks` ,再去查看 `scripts/install-git-hooks` 腳本中的內容,以下我覺得是重點
```
mkdir -p .git/hooks
ln -sf ../../scripts/pre-commit.hook .git/hooks/pre-commit || exit 1
chmod +x .git/hooks/pre-commit
ln -sf ../../scripts/commit-msg.hook .git/hooks/commit-msg || exit 1
chmod +x .git/hooks/commit-msg
ln -sf ../../scripts/pre-push.hook .git/hooks/pre-push || exit 1
chmod +x .git/hooks/pre-push
touch .git/hooks/applied || exit 1
```
我可以看出,在這個專案中,將我們所需的腳本先放在 `scripts` 目錄下,這樣我們 clone 時,就能複製到這些腳本。然後在執行 `make` 的時候,會去執行 `install-git-hooks` , 將 `pre-commit`、`commit-msg`、`pre-push` 用 `ln` 建立連結檔到 `.git/hooks/` 目錄下並改變權限讓其可以執行,如此我們就可以使用這些 client-side hook。
<s>
參考資料 : [探索藏在Git當中的Git Hook](https://hackmd.io/@s716134/githook01)
</s>
:::danger
為何不閱讀 Git 官方的手冊呢?
:::
:::danger
工程人員說話要精準,避免說「感覺」
:::
## 教材啟發
**Indirect pointer 使用時機**
1. 藉由 indirect pointer 來改動傳入的鏈結串列開頭節點,函式就不需回傳值
2. 避免配置暫時節點的空間
3. 實作快慢指標
4. 指向兩條要合併的串列,避免動態記憶體配置
**Rust key concept**
1. Ownership and Borrowing
2. Every variable in rust is immutable
3. Package manager : Cargo
:::danger
你閱讀教材都沒遇到問題嗎?
:::
## 分析及引入 `lib/list_sort.c`
這是 Linux 核心使用的 sort ,是 merge sort ,主要由三個函式 `list_sort()` 、 `merge()` 及 `merge_final()` 組成,這個實作有幾個特別的地方:
在 sort 的時候會先將鏈結串列轉換成 null-terminated 的。
有兩個 merge 的函式,`merge_final` ,會在合併完最後兩個鏈結串列後將結果恢復為標準的 circular doubly linked-list。
`merge ()` 函式 :
`merge()` 的實作方法是用一個無線迴圈,使用 `cmp` 來決定要將 `a` 還是 `b` 放到 `tail`.
這裡有個重點,註解有提到如果有等於的情況,為了排序的 stability 要取 `a`。
值得一提的是 `cmp` 以 function pointer 的型別傳入,定義在 `include/linux/list_sort.h` 裡面:
```c
typedef int __attribute__((nonnull(2,3))) (*list_cmp_func_t)(void *, const struct list_head *, const struct list_head *);
```
在 `linux/lib
/test_list_sort.c` 中可以找到`cmp` 函式,以下是 `cmp()` 的行為 :
當 `cmp` 回傳大於 0 時,表示 a > b 。
當 `cmp` 回傳小於等於 0 時,表示 b < a 。
**補充** :
`__attribute__` 能讓你向編譯器提供關於函式的額外訊息,比如說 `list_sort.c` 中的 `__attribute__((nonnull(2,3,4)))` ,代表告訴編譯器,這個函式的第 2、3、4個參數不能是 `null`。
`likely` 被定義在 `/include/linux/compiler.h` 裡,這個宏定義可以告訴編譯器,你覺得哪個分支更有可能被執行,可以讓編譯器在編譯時,做最佳化,舉例來說,如果在判斷式中使用 `unlikely`,編譯器會調整其編譯出的組語或機器碼,讓 `false` 分支接在後面,使其只有在 `true` 的時候 `jump`。
參考資料 :
[Declaring Attributes of Functions](https://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Function-Attributes.html)
[The Linux Kernel Module Programming Guide](https://sysprog21.github.io/lkmpg/#likely-and-unlikely-conditions)
在我測試完功能要 commit 時,遇到以下問題:
```shell=
Following files need to be cleaned up:
qtest.c
queue.c
list_sort.c:21:20: style: Variable 'head' is not assigned a value. [unassignedVariable]
struct list_head *head, **tail = &head;
^
list_sort.c:11:7: error: Null pointer dereference: (struct element_t*)0 [nullPointer]
la = list_entry(a, element_t, list);
^
list_sort.c:12:7: error: Null pointer dereference: (struct element_t*)0 [nullPointer]
lb = list_entry(b, element_t, list);
^
Fail to pass static analysis.
```
第一個 style 的錯誤訊息是因為沒有賦值給 `head` ,所以我改成 `struct list_head *head = NULL`,就符合 style 了。
接下來兩個錯誤訊息是 cppcheck 檢查的東西,根據 [david965154](https://hackmd.io/@david96514/linux2024-homework1) 的筆記,我了解到可以去 `script/pre-commit.hook` 中設定,`--suppress=nullPointer:list_sort.c"` ,我加了這行讓 pre-commit 對我的 list_sort.c 不檢查 nullPointer 的問題,即可 commit 成功。
## 引入 Tim_sort
使用第一周測驗2的 tim_sort(待辦事項:了解程式碼有沒有問題),引入過程和 list_sort 引入過程一樣。