contributed by < nckusaniel >
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
: 以遞增順序來排序鏈結串列的節點關鍵結構
1.
1.給node配置一個空間
2.檢查空間是否足夠
3.利用INIT_LIST_HEAD將node初始化
1.宣告兩個element_t指標(enrty在前,safe在後)
2.判斷指標l是否為空的
3.利用list_for_each_entry_safe,探索l串列之element_t結構並利用q_release_element 釋放entry
4.釋放l
1.先判斷head是否為空
2.配置空間給q作插入,並判斷q否存在
3.配置空間給value,並判斷value是否存在並將s複製過去
4.利用list_add作插入
一開始不知道要配置空間給value,直接使用q->value=s,導致錯誤的發生,查詢後知道了要先malloc空間給value,再使用strcpy執行陣列的複製
commit時發現因為使用了會導致buffer overflow的函式strcpy,在研讀[https://security.web.cern.ch/recommendations/en/codetools/c.shtml] 後改用strlcpy,而後又發現c99目前無法支援strlcpy,因此改用strncpy
而後發現trace 03會不通過,因此回頭細看q_insert_head是否有錯誤並參考其他同學的code後發現了幾項問題
1.q->value 必須要配置空間,並判斷是否存在,不存在的話執行strncpy會有溢位產生
我希望算出s的大小而後配置給n->value因此寫
發現
1.sizeof(s),算出的s是char*型態的位元組,而非s中有幾個元素
2.sizeof(strlen(s)+1),其中sizeof不是函數,因此不能在裡面放入strlen
3.strlen會回傳size_t而sizeof(size_t)會算出size_t的位元組量,與所求s的大小不相同而導致錯誤?
改寫
刪除sizeof,直接malloc s的字串大小
原理同 q_insert_head,但使用list_add_tail
1.判斷queue中是否有元素
2.宣告target指向第一個元素
3.移除target
4.將target的資料給sp
發現錯誤,改進
1.判斷queue中是否有元素用list_empty來改進
2.使用strnspy
1.道理同remove_head
1事先預設好了
預設
1.先判斷是否為空
2.使用慢指標去追蹤,快指標到終點時,慢指標會指到中間
3.把slow 刪除並釋放
思路
1.先判斷head是否為空
2.使用變數count,目的是希望當目前節點跟下一個節點不同時,去判斷目前節點是否要被刪除
3.宣告兩個指標first,second指向list_head,利用list_for_each_safe探索queue
4.將兩個指標分別利用list_entry取得element_t的address,目的是取用value
5.第一個if中,利用strcmp來判斷first 跟second之中的字串是否相等,若相等就刪除並釋放,且將count+1
6.若字串不相等,必須要利用count來判斷,目前節點的字串之前是否曾經釋放過,是的話目前節點也必須釋放
q_swap
1.檢查head是否存在
2.宣告兩個指標now以及next1,分別指向目前節點,及下一點
3.for迴圈實做,list_del_init(now)使next1跑到now前面,再利用list_add(now,next1)將now加入next1後面,使它們位子交換
這題卡了很久,有以下幾點要注意
1.參考kdnvt的寫法才知道只要先刪除,在插入就可以使兩個點的位子交換了
2.如果queue中有奇數個資料,則最後一項不需要交換
3.在利用list_del_init(now)之後next1會跑到前面
1.用類似交換排序的方式來實做,但是移出比較大小
單純就是把資料放在最右邊
思路
1.探索每一個點,將其一個一個插入到最前面
原本用打算依序將節點移動到最後面用list_move_tail實做,但是終止條件有點難寫
q_sort
1.交換排序實做
qsort
1.參考你所不知道的 C 語言: linked list 和非連續記憶體之中的quicksort實做
執行後發現trace-14以及trace-15過不了,因為執行時間太久,必須要用複雜度更低的方法,因此在研讀你所不知道的 C 語言: linked list 和非連續記憶體決定使用merge sort來實做
參考Risheng1128 來糾正我原本的錯誤
思路
1.注意邊界條件
2.因為circular doubly linked list,有pre以及next兩個指標,並且head以及tail相接,在執行上會比較複雜,因此將circular doubly linked list先改成singular linked list。
並且在執行完merge_cut之後再進行link的調整
起初我的想法是直接head= merge_cut(head);
而後發現把head也丟進merge_cut以及merge_twolist之中,會有更多的邊界條件要處理,因為list會一直被切割,並且只有某些list有head。因此使用head->next = merge_cut(head->next); 來解決head的傳入問題
最後會發現link list 所有的prev都沒有指向正確的地方,並且首尾沒有相接所以執行以下
這邊是傳入原本List中的head->next,並且tail->next已經被改成NULL,因此不能使用list_empty(node1),原先使用list_empty(node1)導致一直出錯,之後利用GDB檢查之後才發線問題所在
1.mid是後面串列的開頭,所以有fast->next; fast = fast->next->next兩個結束循環的可能性
2.將slow的next設為NULL,來確保遞迴呼叫時所有tail的next都是NULL,來避免邊界條件
1.利用[指標的指標],來實作兩個list的合併
2.因為ptr = &head,所以最初ptr就是代表head
3.而if之中的*ptr則是指node中的next指標
4.if之中只有改變next指標,並沒有改變pre指標,所以qsort 之中才要去將所有prev指標做調整
最後當l1或l2為NULL時(也就是ptr所停在的地方),我們要探討到底是將l1連接到l2抑或是l2連接到l1。
將list1及list2做OR運算,並且轉換型態存給ptr
研讀 Linux 核心原始程式碼的 lib/list_sort.c 並嘗試引入到 lab0-c 專案,比較你自己實作的 merge sort 和 Linux 核心程式碼之間效能落差
在 qtest 提供新的命令 shuffle,允許藉由 Fisher–Yates shuffle 演算法,對佇列中所有節點進行洗牌 (shuffle) 操作
在 qtest 提供新的命令 web,提供 web 伺服器功能,注意: web 伺服器運作過程中,qtest 仍可接受其他命令,可依據前述說明,嘗試整合 tiny-web-server
運用 Valgrind 排除 qtest 實作的記憶體錯誤,並透過 Massif 視覺化 “simulation” 過程中的記憶體使用量,需要設計對應的實驗
解釋 select 系統呼叫在本程式的使用方式,並分析 console.c 的實作,說明其中運用 CS:APP RIO 套件 的原理和考量點。可對照參考 CS:APP 第 10 章重點提示
研讀論文〈Dude, is my code constant time?〉,解釋本程式的 “simulation” 模式是如何透過以實驗而非理論分析,達到驗證時間複雜度,需要解釋 Student’s t-distribution 及程式實作的原理。注意:現有實作存在若干致命缺陷,請討論並提出解決方案;
指出現有程式的缺陷 (提示: 和 RIO 套件 有關) 或者可改進之處 (例如更全面地使用 Linux 核心風格的鏈結串列 API),嘗試實作並提交 pull request