contributed by < cy023
>
queue.[ch]
和連帶的檔案,測試後用 Git 管理各項修改,要能滿足 $ make test
自動評分系統的所有項目。
lab0-c
專案,比較你自己實作的 merge sort 和 Linux 核心程式碼之間效能落差qtest
提供新的命令 shuffle
,允許藉由 Fisher–Yates shuffle 演算法,對佇列中所有節點進行洗牌 (shuffle) 操作qtest
提供新的命令 web
,提供 web 伺服器功能,注意: web 伺服器運作過程中,qtest
仍可接受其他命令
qtest
執行過程中的錯誤
qtest
再於命令提示列輸入 help
命令,會使 開啟 Address Sanitizer 觸發錯誤,應予以排除qtest
實作的記憶體錯誤,並透過 Massif 視覺化 "simulation" 過程中的記憶體使用量,需要設計對應的實驗queue.c
實作
q_new
: 建立新的「空」佇列;q_free
: 釋放佇列所佔用的記憶體;q_insert_head
: 在佇列開頭 (head) 插入 (insert) 給定的新節點 (以 LIFO 準則);q_insert_tail
: 在佇列尾端 (tail) 插入 (insert) 給定的新節點 (以 FIFO 準則);q_remove_head
: 在佇列開頭 (head) 移去 (remove) 給定的節點;q_release_element
: 釋放特定節點的記憶體空間;q_size
: 計算目前佇列的節點總量;q_delete_mid
: 移走佇列中間的節點,詳見 LeetCode 2095. Delete the Middle Node of a Linked Listq_delete_dup
: 在已經排序的狀況,移走佇列中具備重複內容的節點,詳見 LeetCode 82. Remove Duplicates from Sorted List IIq_swap
: 交換佇列中鄰近的節點,詳見 LeetCode 24. Swap Nodes in Pairsq_reverse
: 以反向順序重新排列鏈結串列,該函式不該配置或釋放任何鏈結串列節點,換言之,它只能重排已經存在的節點;q_sort
: 以遞增順序來排序鏈結串列的節點,可參閱 Linked List Sort 得知實作手法;建立一個佇列起點 (struct list_head
型態)。
佇列內不含其他資料,並將起點的 prev
, next
指向自己。
一開始配置 element_t
型態大小的空間給 queue head
, 並將 element_t
的 value
元素指向 NULL
。
但後來發現完全不必要額外浪費不使用的空間(一個字元指標大小),queue head
設定為 struct list_head
即可。
(若在其他應用場景可以將queue head
型態改為包含 stuct list_head
與其他佇列額外資訊的結構型態,在使用 container_of
取得需要的資訊)
並使用 list.h
中 Linux Kernel 風格 API INIT_LIST_HEAD()
初始化 queue head
。
除了張貼程式碼,你應該解釋其作用,並探討後續的改進空間。
釋放所有佇列使用到的空間。
q_free
函式傳入參數為 struct list_head *
型態,但佇列中用來儲存資料的節點型態是 element_t
,需要注意所有佇列中的字串都要被釋放,以防 memory leak
。
需要實作利用結構成員實際位址,找出該結構物件的起始位址的操作。
參考 list.h
中,container_of
。
使用標準 API list_empty()
檢查佇列是否為空,如果佇列內還有資料,使用 list_first_entry()
取出第一筆資料 (element_t
型態), list_del()
將第一筆資料移出 queue
,然後釋放掉該節點使用的空間,最後釋放 queue head
。
將 疊代 迭代方式改為標準 API,list_for_each_entry_safe()。
配置 element_t
型態空間當作新節點,另外配置傳入字串長度的空間用來存放節點的字串資料。
複製字串時考慮 buffer overflow 議題,使用 strncpy()
函式。
設定好新節點後,用標準 API list_add
將節點插入佇列。
與 q_insert_head
類似,將新增節點利用 list_add_tail()
插入佇列尾端。
利用前述提到的 list_first_entry()
取出佇列中的第一個節點,並使用 list_del()
從佇列中移除 (remove)。
在將被移除節點字串內容複製出來時,使用 strncpy()
函式,根據 man strncpy
The strcpy() function copies the string pointed to by src, including the terminating null byte ('\0'), to the buffer pointed to by dest. The strings may not overlap, and the destination string dest must be large enough to receive the copy. Beware of buffer overruns! (See BUGS.)
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.
需要注意,當被移除節點字串長度大於 buffer 長度時,strncpy()
並不會在 buffer 最後一位補上 '\0'
結尾,需要幫忙加上。
與 q_remove_head
類似,以 list_last_entry()
取出佇列中的最後一個節點,並使用 list_del()
從佇列中移除 (remove)。
list_for_each()
走訪所有節點,並計算長度。
LeetCode 2095. Delete the Middle Node of a Linked List
藉由 Cycle detection 演算法的機制,找出 queue
的中間節點,並將其 delete
(不只是 remove
)。
如果節點數量為奇數,移除第 ⌊n / 2⌋
個節點。
LeetCode 82. Remove Duplicates from Sorted List II
TODO: 探討與 quiz1
遞迴版本的差異
在看 quiz1 的時候,突然發現這裡寫錯了…
重複的節點需要全部移除,但以上的實作,重複的節點會有一個被保留下來。
回頭看 ./qtest
的 do_dedup()
只有檢查是否存在重複節點,若留一個重複節點下來,./qtest
並不會發現。
改成
LeetCode 24. Swap Nodes in Pairs
一次跨兩步 剛開始將 node
指向 head
,先檢查是否存在下下個節點 (node->next
不等於 head
且 node->next->next
不等於 head
)。
若下下個節點存在,使用 list_move()
將當下節點 (node
) 與下一個節點 (node->next
) 交換,再將 node
指向下下個節點。
按照這個順序走訪並交換,直到下次執行會回到 head
或超前 head
。
當節點有奇數個,代表迴圈會因為 node->next
等於 head
而停下,此時最後一個落單的節點不會被交換。
當節點有偶數個,代表迴圈會因為 node->next->next
等於 head
而停下,在停下前,迴圈會交換每對的節點。
LeetCode 206. Reverse Linked List
效能分數當然拿不到 XD,留個紀錄。
參考 案例探討: LeetCode 21. Merge Two Sorted Listssw34,改進成比較有品味的程式。
shuffle
在 qtest.c
內新增函式 do_shuffle()
,
並在 static void console_init()
函式內新增 shuffle
命令。
修改後就能使用 help
查詢到 shuffle
命令了:)
實作 shuffle
演算法,一開始想要擺在 queue.c
,但 queue.h
不能改,結果最後直接放在 qtest.c
的 do_shuffle()
內。
tiny-web-server
Linux 核心原始程式碼巨集: container_of
你所不知道的 C 語言: linked list 和非連續記憶體
:)
cat scripts/pikachu.raw