contribyted by < nckuoverload
>
sysprog2019
queue.[ch]
和連帶的檔案,測試後用 Git 管理各項修改。
qtest
的行為和裡頭的技巧,特別是 signal handlerMAGICHEADER
, MAGICFREE
, MAGICFOOTER
, FILLCHAR
等巨集的使用,並探討其動機和用法Operating System: Ubuntu 16.04 64bit
Compiler: gcc 8.3.0
在queue.h
中,修改queue_t
結構。
新增list_ele_t *tail;
用來指向尾巴。
新增int size;
用來記錄目前的長度。
首先是建立一個queue ,過程沒太大問題,要注意malloc
有可能會返回NULL
,所以在第29行要針對這個情況特別處理。
釋放佇列,主要使用一個loop 去對每一個節點做處理。
和_insert_tail()
可以搭配一起來看,主要在第一個if
判斷該佇列是否為空,第二、三個判斷malloc
是否返回NULL
(在測資中,會針對這個部分做測試)。
從頭插入的部分主要是新增一個節點,並將該節點指向原本的head ,並將佇列的head 指向該節點,並且size 增加。
和q_insert_head()
相似,前三個判斷式相同效果,但應該有更好的寫法。
從尾端插入的部分,主要是將原本的尾端指向即將插入的節點,並將佇列中的tail
改成指向即將插入的節點。
主要要注意測試中,有兩種測試方式,一個為rh [str]
和rhq
,前者主要會和輸入的字串做判斷,在這個部分要特別注意輸入字串的大小是否大於佇列中第一個節點的大小,在第147行針對這個部分做處理,如果超過不處理的話會造成溢出(overflow)。
使用額外的int size
來儲存佇列大小,為了符合題目的。
總共寫了兩個版本,原先使用類似bubble sort 的演算法來做倒轉,簡單來說就是模擬bubble sort 最壞情況,每一個迴圈讓一個節點移到適當的位置,並讓其他節點往前移一個位置,複雜度為。但這樣做的話在test 會一直因為時間超過而無法通過。
第二個版本主要是多用了三個暫存變數,每一次將一個節點的指標轉向,並在最後處理q
的head
和tail
。也是很直觀的做法,但一開始因為題目敘述理解錯誤,誤解為不能多使用額外空間設置變數來處理。
主要使用Makefile 來自動評分,Makefile 寫起來類似shell script 但又不盡相同,詳細可以參考這篇範例,或是網路上應該有許多其他解說。
在原始的Makefile 中,主要是編譯後,呼叫scripts/driver.py
來評分,所以也可以在~/lab0-c
中直接使用python driver.py
來評分。
driver.py
又可以使用四個參數來使用。
h
: 主要用來說明有哪些參數,類似一般使用的help 。
p
: 可以選擇用哪個檔案來評分。 (不太確定式不是這個意思)
t
: 可以選擇要選第幾個腳本來測試,腳本都在~/lab0-c/traces
底下。
v
: 有0~3的級別,可以選擇要顯示多少測試的腳本資料。
A
: 自動評分,功能等同於使用 make test
。
--valgrind
: 如果直接搜尋會找到 Wikipedia 的解說,是一款GNU 的工具,用來處理記憶體除錯。稍後會再針對這部分解釋。
qtest
的行為和裡頭的技巧,特別是 signal handler在 qtest.c
中,會有許多 method 用來做測試,每一個都會使用 exception_setup() exception_cancel()
兩個來對 exception 做處理,同時裡面也有使用到 signal 的機制。
首先會先執行 queue_init()
並且呼叫兩個 signal ,SIGSEGV
用來處理記憶體部分的例外狀況;SIGALRM
用來處理執行時間的例外狀況。
之後以 do_new()
為例,在第107行的部分,當要執行 q_new()
前,會先執行 exception_setup(true)
,用來開啟 signal ,並且在執行完成後使用 exception_cancel()
關閉。
exception_setup()
和 exception_cancel()
都在 harness.c
中。以下將分別敘述。
exception
發生時的狀況。
do_new()
等執行 exception_setup()
跳入,因為是正常情況,所以 sigsetjmp()
並不會被執行,會直接跳到253 行執行,jmp_ready
參數主要是用來紀錄在 exception 發生後,是否可以執行 jump 。並且這時候會在257 行給予一個執行時間,程式必須在時間內完成,否則會觸發 signal 並且丟出 sigalrmhandler
讓 qtest.c
中的 sigalrmhandler()
去處理例外。並且會將狀態恢復到未執行 q_new()
前的狀態繼續往下執行。sigsetjmp
來保存 stack 環境等,所以會把剩下的時間返回,並且將錯誤訊息印出,之後在兩個 handler 中都有使用到 trigger_exception()
去處理,trigger_exception()
會使用 siglongjmp
將環境恢復到原本儲存的狀態(尚未進入會發生例外狀況的 method 前的狀態)。小結, qtest.c
因為不能確保每次執行實作者在 queue.c
中實作的 method 都是可靠的,所以每次要進入 queue.c
的方法前,都會先使用 sigsetjmp
保存狀態,並且開啟 signal ,如果例外狀況沒發生,則會使用 exception_cancel()
去關閉所有上述機制。如果例外狀況發生,則會使用 trigger_exception()
中的 siglongjmp()
來恢復到進入有問題方法前的環境。
q_reverse
的部分改回第一版本氣泡排序法去執行,可以發現並不會有時間錯誤的問題出現,在第13個測試 trace-13-perf.cmd
正常情況下會發生錯誤。q_reverse()
using bubble sorting