Linux
, system programming
contributed by < Randy870819
>
快要完成作業實作與實驗後才發現有了 Makefile
才讓整個專案開發如此順暢,且為了做專案的修改實驗也需要修改 Makefile
,因此將 Makefile
的分析提前放到此處,也感謝同學 KYG-yaya573142 的共筆。
Makefile 語法在老師提供的 Makefile 語法和示範 已有詳盡的介紹, lab0/Makefile 。
一開始先對常用或是冗長的 變數(巨集) 做定義。
第一次執行 make
會先行安裝 Git Hooks:
執行 make
後,make
會在 makefile 中找尋第一個目標 (target) ,亦即將 all
預設為最終建制目標,因此除了編譯 qtest
外,還會安裝 Git Hooks 。
參數設定: VERBOSE and SANITIZER
make
並附加 VERBOSE=1
會讓顯示詳細編譯的過程:
@
加在指令前,會讓指令不顯示,因此利用 Macro
附加在之後會執行的指令前,便能輕易控制是否顯示。@true
能追加該行指令直接被視為成功的作用, @printf
則可以印出相對應字串。make
並附加 SANITIZER=1
則會對編譯參數做修改。Objects and Dependencies: 這邊使用了 Auto-Dependency Generation 來解決 dependency 的問題。
deps := $(OBJS:%.o=.%.o.d)
使用萬用字元 %
配上之前建立的變數,為所有 objective file 設立好它 dependency file 的檔名,以備之後 include 。deps
設定一樣,接下來才能順利使用。 ( 註:建立 dependency file 是編譯器中 pre-processor 的工作 )當一個目標 (target) 比其自己的相依項目 (dependency) 更新時間還要早時,代表其相依項目已被修改,要重新建立目標,但一個專案往往會有眾多 dependency file ,而合以上流程,就是代替我們完成建立相依性的複雜工作!
Valgrind 支援
valgrind_existence
: 檢查作業系統有無安裝 Valgrind 。
man which
which
指令讓使用者得知某一 command 執行的路徑。2>&1
則是代表將 stderr
導入 stdout
。 (From: Stack Overflow)/dev/null
是 NULL Device ,會回報丟入資料是否成功,使用以下輸入如果電腦中已有安裝 Valgrind 則會得到 yes
輸出喔。
||
代表若左邊執行出錯,就執行右邊的內容。valgrind
$(MAKE)
為 Recursive make commands ,所以要小心不要再次執行 make
建立同樣的目標,會造成無限迴圈。 Manual$(eval expression)
則是會將右邊 expression 展開並當作 Makefile 指令執行。 Manualsed -i "s/alarm/isnan/g" $(patched_file)
: 其中 "s/alarm/isnan/g"
並非路徑,而是 sed
(stream editor) 替換字串的語法,在此是將 $(patched_file) 中的所有的 "alarm" 替換為 "iasnan" 。發現同學 frankchang0125 解釋的更為清楚。
queue.h
:queue structure為了讓 q_insert_tail()
跟 q_size()
執行時達到 的時間複雜度,增加了變數 size
與 tail
指標便於存取。
q_new
建立一個新的 queue_t
結構,並對其初始化,若 malloc()
回傳 NULL ,代表配置記憶體失敗,回傳 NULL 。
q_free
一開使先對沒有 queue 的特定情況做處理;在 queue 被建立的情況下,走訪整個 linked list 利用 pre
指標指向前一個 list element 在對其指到的記憶體做 deallocation ,最後再清除 queue 結構。
考慮以下變更:
要點:
list_ele_t *pre
的 scope;while
改寫為 for
迴圈,更好閱讀且程式碼縮減;NULL
時,該函式無作用,於是就可利用此特性減少一個 if
敘述;q_insert_head
一開使先對沒有 queue 的特定情況做處理,再依序新增 list_ele_t
與字串,要注意的是如果其中某一環節出錯,必須將在這函式中新增的記憶體位置都釋放掉。
接下來使用 strncpy()
將輸入字串複製到新增的字串中,要注意的是 strncpy()
只會複製指定長度 個字元,必須自行注意字串結尾有沒有 null character ('\0')
:
The
strncpy()
function is similar, except that at most n bytes of src are copied. Warning: If there is no null byte among the first n bytes of src, the string placed in dest will not be null-terminated. Linux man page
最後插入到頭端後要特別注意 linked list 長度由 0 變 1 的情況,此時頭與尾端是指向同一個 list node 。
q_insert_tail
此實做跟 q_insert_head()
幾乎一致,最後插入到尾端後也要特別注意 linked list 長度由 0 變 1 的情況,此時頭與尾端是指向同一個 list node 。
q_remove_head
再處理完沒有 queue 或是 queue 為空的情況後,便能使用 strncpy()
複製,並在字串尾端加上 null character ('\0')
。
接下來移除頭端 list node 要注意必須確實將配置在該 list node 上的記憶體清除,亦即清除完字串後,再將 list node 清除;最後在更新 list size 時也要注意當 linked list 長度由 1 變 0 ,我們要將 queue 中頭端與尾端的指標更新為 NULL
,避免之後又存取已清除的記憶體造成 Segmentation fault 。
q_size
檢查完 queue 尚未被建立的情況後,便直接回傳 size 。
可改寫為以下:
簡潔又清晰!
q_reverse
一開使先對沒有 queue 或是 linked list 內只有一筆資料的特定情況做處理。
再利用以下3個指標持續走訪 linked list 並反轉其連結方向:
利用以上 3 個指標,在走訪 linked list 依序平移並將 linked list 反轉。
TODO: 用 for
改寫並縮減 scope
q_sort
q_sort
: Top level interface.q_sort()
為排序的 Top level interface ,除了檢查沒有 queue 或是長度為 1 不需要排序的情況,便進到 Merge sort 排序,但排序後會失去 linked list 尾端的資訊,因此需要重新走訪 linked list 找回尾端指標。merge_sort
: Recursive merge sort (Divide and Conquer).merge
: Merge 2 linked list according to natural ordering.list_ele_t
的指標 head
指向 NULL , 再另外使用一個 double pointer (pointer to pointer) cursor
來記下目前合併 linked list 進行到的位置,接下來便能走訪兩個 linked list ,依據定義好的 ordering 做合併,利用 cursor
更新目前尾端的 list element 的指標;最後當某一個 list 已經走完,因為兩個 linked list 早已做完排序,便能直接另一個 list 附加到尾端,結束合併,再回傳合併後的頭端指標。str_cmp
:null character ('\0')
的情況。q_sort()
(2/23)一開始看完 Linked List Sort 的介紹其中的圖片便著手開始實現沒有遞迴呼叫的 merge sort ,使用 Bottom-up 的方式, for 迴圈第一次 iteration 為每個長度為 2 的 sub-list 完成排序,第二次 iteration 為每個長度為 4 的 sub-list 完成排序,依此類推 (即 for 迴圈中 k*=2
)。
From: Linked List Sort
但是觀察以下程式碼便會發現在排序對象是 linked list 而非一般陣列的時候,會在找尋左右要合併的 partition 中花很大的功夫,並且排序生成的樹狀圖形會如同上圖般為一棵不平衡的樹,在時間複雜度上會多一些 lower order 的項次,而且程式碼還比較複雜 (本身功力不足也有關係 QQ )。
因此最後還是好好的利用遞迴,將 merge sort 依各部功能實現。
採用 Natural Sort 來為字串進行排序 (連結內已有詳細敘述) 。
將 strnatcmp.c 與 strnatcmp.h 加入專案。
修改 Makefile ,加入 strnatcmp.o
進 $(OBJS)
以便專案建制。
接下來就可以在專案中的 queue.c
引用 strnatcmp.h
並使用其函數囉,有趣的是在 strnatcmp.c
中有兩種比較 Natural ordering 的函式分別為 strnatcmp()
與 strnatcasecmp()
,深入去看可以發現 strnatcasecmp()
其實是把字元轉換為大寫 (uppercase) 後再做比較,因此像是底線 _
這個常用在檔名的字元與一般英文字母比較,優先度就會低,在這邊我們就採用 strnatcasecmp()
。
接下來我們可以為 Natural Sort 裡提供的測資創建新的測資檔案並在 driver.py
中加入相對應的檔名與分數等。
另外要注意有測資檔後,還必須要修改 qtest.c
中為 sorting 結果做檢查的部份,因為一開始是使用 strcasecmp()
而非 strnatcasecmp()
。
最後便能順利使用 Natural sort 做測試。
但是目前還是有一個問題未解決,就是採用 Natural Sort 後會讓測試項目編號 15 trace-15-perf
TLE ,去除掉一般字元比較的部份,可能的原因是做完一般字元 prefix 檢查後,便須要對剩餘的數字字串 (digits) 做大小的比較,因此此處也是之後可以加強的部份。
TODO:
研究是否能再提升 Natural Sort 的效率。
使用以下指令運用 Valgrind 找尋出錯的地方,到第3筆測資 trace-03-ops 時遇到:
回追至 q_reverse()
發現是在反轉 linked list 最後還會去對尾端節點的指標 next 會指向 NULL
我卻還去存取造成 Segmentation fault 。
經過些微修改解決記憶體存取的問題。
TODO: 用 diff (HackMD 支援該語法標示) 來展現程式碼之間的差異
開啟 stack flag 觀察 merge sort!?
XOR Linked List: 利用 exclusive or 的特性,我們可以達到用 single pointer 除存 double pointer 的效果,缺點是必須要有得知兩個相鄰的 list node 才能繼續推理 (inferencing) 之後的 list node 位址。