contributed by < DennisLiu16
>
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 得知實作手法;shuffle
,能透過 Fisher–Yates shuffle 對佇列中所有節進行洗牌 (shuffle) 動作web
,提供 web 伺服器功能,注意: web 伺服器運作過程中,qtest 仍可接受其他命令,並嘗試整合 tiny-web-server – github必要套件
Cppcheck
q_new
思路
回傳型別是 list_head *
且沒有引數,需使用 malloc 在 heap 上申請大小同 struct list_head
的記憶體空間,malloc 在某些情況下會回傳 NULL,需於 INIT_LIST_HEAD
初始化前檢查,避免輸入 NULL 造成例外。
void *
的思考
malloc 回傳 void *
型別指標,根據 你所不知道的C語言:指標篇 – void *
之謎,void *
的存在是確保使用者確實轉型,那是否需要進行強制轉型呢?
參考問題 Do I cast the result of malloc? 下的三個回答:
Issue of cast and sizeof. (Someone said this answer should update)
問題使我困惑,目前對本例(即 malloc)而言,評論中贊同 C 中要 casting 主要立場是 C/C++ 有更好的相容性和能讓編譯器提出轉型相關的警告。不贊同要 casting 的主張 malloc 內部已經包含能使 void *
安全轉型成各種 pointer 的實做,因此不需要多此一舉,造成閱讀困難。目前看起來不應該使用 casting 比合理,因為可以透過 extern 界面達到 C/C++ 的兼容,所以看起來沒有一定要使用 casting 的理由?
C 和 C++ 已是截然不同的程式語言,在 C++ 混用 malloc 可能會造成非預期的行為,另外,C++ 有專用的運算子,如
static_cast
,本質上不該仰賴 C 語言風格的 casting。本課程要求學員撰寫出簡短、有效,和符合規格的程式碼,因此你不需要額外對void *
型態的malloc
返回值進行 casting,因為編譯器會幫你做。不用看太多網際網路上面的討論,應該儘量閱讀規格書並運用其中的條款來分析程式碼的運作。
:notes: jserv
一致的是,大家建議在 sizeof
中使用 dereference of lvalue,避免更動左值時忘記更動型別。
INIT_LIST_HEAD
的變革 (WRITE_ONCE 的使用)
最新版本中(v5.17-rc5),被定義於include/linux/list.h
之下的INIT_LIST_HEAD
的程式如下
而在 v4.5-rc1 之前的版本如下
程式
q_free
list_entry 透過
container_of
與輸入list_head *
可返回指向 entry「結構體」指標,本作業中即返回指向結構element_t
的指標,而非返回指向 list_head 的指標。可使用該返回值對結構體進行->
操作。
list 之中有眾多走訪巨集,以下分析適用情境
此種走訪適用於讀取鏈結串列資訊 (read only) e.g. q_size
,註解同樣提到若改動 list 中的 element (list_head) 可能產生 UB。
The nodes and the head of the list must be kept unmodified while
iterating through it. Any modifications to the the list will cause undefined
behavior.
member
是 list_head
在 entry
的變數名此種走訪適用於讀取結構體參數 (read only),註解提到若改動 list 中的 element (list_head) e.g. 指標指向,則可能產生 UB。
list_for_each_safe
此種走訪適用於更改鏈結串列資訊 (R/W),透過多輸入 safe
指標儲存下一個 list_head
node 確保修正不造成鏈結串列毀損
list_for_each_entry_safe
此種走訪適用於更改結構體參數 (R/W),可修改原理同上
思路
釋放結構體時,需考慮結構體中的指標是否由 malloc
動態配置,因此要觀察 queue 內的元素組成。由 q_remove_head
可知佇列中的元素類型 element_t
(包含 list 和字串), queue.h
中的定義為:
value
用於保存字串,需由我們進行配置和「刪除」,因此刪除節點前應先刪除 char *
記憶體空間element
是透過 q_insert_xxx
申請的l
也是我們利用 malloc
申請的因此三者皆需刪除。由於需要修改 (free) entry
內部,因此要用 list_for_each_entry_safe
走訪。
free
可以用 q_release_element 代替
程式
q_insert_head
思路
head->next
,對應list_add
不使用 strcpy
的原因是容易造成記憶體溢位。代替方法可以使用 strlcpy
,透過直接輸入長度確保不溢位。CERN 有提供 strlcpy
的實做
由 snprtinf
實現所以會自動添加中止符'\0'
,即僅複製 sz - 1
個字元,以下是測試:
程式
q_insert_tail
思路
q_insert_head
程式
q_remove_head
思路
list_first_entry
得到首個 entrylist_del
移除該 entrysp
地址有效才複製strlcpy
會自動加上 '\0'
,因此不用特別修改 bufsize
程式
q_remove_tail
思路
q_remove_head
,重複程式之後再透過其他函式包裝?程式
q_release_element
q_size
q_delete_mid
思路
head
是否有效list_for_each
初始式先走快指針,於迴圈中再一起走一步,之後靠迴圈和迴圈內雙重檢查可以讓佇列中不論是奇數或偶數都讓慢指針停在正確的位置list_entry
得到 entry指標,並透過 list_del 移除該entryfree
或 q_release_element
都可以透過 indirect pointer 的實做待補
程式
q_delete_dup
思路
list_for_each_entry_safe
注意事項
list_for_each_entry_safe
中僅保證對 entry 的操作安全,並不保證對 safe 的操作是安全的
會得到
程式
q_swap
q_reverse
q_sort