contributed by < mtbehisseste >
linux2020
作業說明 : lab0-c
Docker for Mac
git hook 是一些在特定 git 操作時會執行的腳本,對於開發中的一些規範或風格都可以透過 git hook 來達到自動化檢查。當執行 git init
時,在 .git/hooks
底下會生成一些範例腳本( *.sample
)。若要使用客製化腳本的話,則將檔案放在 .git/hooks
下即可(檔案須為 executable)。本專案則是用 link 的方式將檔案連到統一放置的 scripts/
目錄下的執行檔。
觀察一下腳本內容: commit-msg.hook
檢查 commit message 是否符合裡面的 9 個規則,包括使用祈使語氣、句尾不加句號等等。 pre-commit.hook
使用 cppcheck
、 aspell
、 clang-format
等工具做檢查,也過濾 unsafe function 的使用。另外
的用法可以只檢查現階段還沒被 commit 的那些 c/cpp/h 檔,有 cache 的效果。
pre-push.hook
會先確認是不是從 sysprog21/lab0-c 某次 commit 之後才 fork 的。而如果要 push 到 master 的話會先做一次 make
確保遠端的程式碼是可以成功編譯的。
自動化將 trace 輸入到 qtest
的腳本,裡面使用 subprocess.call()
來執行 qtest
的指令。因為平時我多用 os.system()
所以我順手搜尋了兩者的比較:
os.system()
會產生一個 subshell 來執行指令,底層實作是呼叫 C 的 system()
,所有產生的結果會在 stdout 輸出subprocess.call()
(Python 3.5 後可用 subprocess.run()
代替)除了在給定參數 shell=True
之外,不會產生新的 shell 。另外也有 capture_output
等參數可以選擇,功能也比 os.system()
多Reference: https://docs.python.org/3/library/subprocess.html#subprocess.run
另外這邊使用 getopt()
來解析命令列參數,與我平時使用的 argparse()
不同,使用方法與 C 語言中相同:
args
是待解析的參數 list,通常傳入 sys.argv[1:]
shortopts
是一串待辨認的字母選項,若字母後有冒號表示該選項後面需接上參數。如 hp:t:
, h
後不需接東西, p
及 t
則要longopts
為支援的選項名字,以 list 表示。如 ['valgrind']
代表可以用 ./driver.py --valgrind
。若 longopts
須帶參數,則在字串後加上等號最後回傳一個 (option, value) pair 的 list 以及剩餘的 args。
過去在寫程式的時候一直希望自己能夠保持一致的程式風格,但卻都沒有參閱 Google C++ Style Guide 這類大型專案所採取的風格規範,導致自己可能會憑"感覺"去寫,像下面這樣的情況就會發生:
使用 clang-format
讓一些沒注意到的地方可以自動改成符合風格的,且在 sysprog21/lab0-c
最近的 commit 提到,可以將 clang-format
與 vim 整合,對於往後程式開發更加便利。
queue 結構包含兩指標 head
、 tail
及一整數 size
, tail
為方便實作 q_insert_tail
及 q_reverse
等; size
為使 q_size
複雜度為 :
初始化 queue,首先宣告一 queue_t
結構的指標 q
指向一塊記憶體區塊,其大小為 queue_t
結構的大小;並分別初始化 queue_t
中每個成員。
將傳入的指標 q
所指向的記憶體釋放,不斷呼叫 q_remove_head
直到其回傳 false
(當 q
為空或 NULL),並 free(q)
。
在 ISO/IEC 9899 7.20.3.2 中提到:
The free function causes the space pointed to by ptr to be deallocated, that is, made available for further allocation. If ptr is a null pointer, no action occurs.
因此在 free(q)
前我們不必費心考慮 q
為空或 NULL。
產生一新 element newh
,將給定的字串複製到 newh
中的 value
成員並將 newh
接在 q
的頭。若 q
為 NULL 或無法配置記憶體則回傳 false,成功回傳 true 。
首先要 malloc 一塊 list_ele_t
結構大小的記憶體並由 newh
指向,接著 malloc 一塊大小為 (strlen(s) + 1) * sizeof(char)
的記憶體,由於 char
為 1 byte,可以直接用 s
的長度來知道所需記憶體大小。而 strlen
只回傳字串本身,並不包含字串後的 terminator,因此要多要 1 byte。這邊要注意的是:若是 malloc 失敗,則必須將剛剛 malloc 的 newh
的記憶體也釋放掉,並且將 newh
指向 NULL
,這是我一開始疏忽掉的。
字串複製我使用 strncpy
,相較於 strcpy
,指定複製長度更安全。長度則是 s
長度加上 terminator。另外我也有看到其他同學使用 memcpy
不過我想既然是字串的處理使用專門處理字串的函式可以讓未來更方便。
最後將 newh
擺到 q
的頭並增加 q->size
即可,要注意的是若 q
目前為空,newh
將成為 q
中唯一元素,則 q->tail
也必須指向 newh
。
作法與 q_insert_head
雷同,當 q
為空亦須將 q->head
指向 newt
,唯最後 newt->next
指向 NULL 並成為新的 q->tail
。
當 q
為空或 NULL 則回傳 false,否則新增一指摽 newh
指向新的 q->head
(即 q->head->next
)。當 sp
為非 NULL,代表對應到 qtest
中的 rh [str]
命令,須將 head->value
複製到 sp
中。使用 strncpy
複製長度 bufsize - 1
的字串到 sp
,最後再加上字串 terminator。
最後 free 掉 q->head->value
及 q->head
指向的元素,將兩者指向 NULL
,並將 q->head
指向 newh
。
函式中的註解要求此函式必須為 ,因此在 struct queue_t
另外新增一成員 size
是必要的,每次 q_size
只需回傳該成員即可。
這邊用到資料結構中的反轉 linked list,宣告三個指摽 pre
、 cur
、 pos
來操作,流程如下:
q_sort
原本使用 bubble sort 但由於時間複雜度太高,因此後來選擇 merge sort 實作。 merge_sort
參數為 q->head
的 reference,在 merge_sort
結束後不必多餘的指派, q->head
即指向整個排序後的 queue。
merge_sort
流程為將 queue 平均分為兩 queue,分別由 front
、 back
指向;再分別對兩 queue 遞迴的作 merge_sort
直到 queue 為 NULL 或剩餘一元素。
參數的 **q_head
為一指向 head pointer 的指標,目的是為了將 head pointer 本身傳入,並且在函式結束後讓 head pointer 指向最終結果。 後面遞迴的時候 front
及 back
會分別代表新的 head pointer ,最後 merge
會將兩 queue 合併並回傳排序後 queue 的 head pointer,指派給最初傳入的 *q_head
。
split_queue
將 q_head
指向的 queue 均分為二,方法為使用兩指標 fast
及 slow
並讓 fast
以兩倍 slow
的速度迭代整個 queue,當 fast
為 NULL 代表到底了,此時 slow
會指向中間元素的前一元素。將 q_head
指派給 *front_p
; slow->next
指派給 *back_p
,並將 slow->next = NULL
可分為兩 queue。
要注意的是 split_queue
後兩參數為 pointer to front
及 pointer to back
並且 front
及 back
亦分別為指標,因此呼叫的時候是把 front
及 back
指標本身的 reference 傳入:split_queue(head, &front, &back);
。且在最後是使用*front_p
及 *back_p
來拿到 pointer 本身。
merge
將傳入的兩 head pointer 指向的 queue 合併,首先決定新 queue 的 head 並由 result
、 current
兩指標指向。並且在分別迭代 a
、 b
兩 queue,比較大小決定 current->next
直到其中一者取完為止,並將另一者剩餘部分接在新 queue 後完成,最後 result
會指向新 queue 的頭,回傳後會直接指派給 merge_sort
的 *q_head
(即傳入 merge_sort
的 q->head
)完成排序。
另外原本使用遞回版本的 merge
會遇到 stack 空間不足的情況。