contributed by < sternacht
>
依據指示著手修改 queue.[ch] 和連帶的檔案,測試後用 Git 管理各項修改,記得也該實作 q_sort 函式。
列出其他在作業要求中出現的操作,例如 LeetCode 題目。
向記憶體索取大小為 element_t 的空間,並檢查是否成功,若非則回傳 NULL ,是則進行初始化並回傳
git commit 時出現以下警告
queue.c:25:5: error: Memory leak: new [memleak]
return &(new->list);
^
修改為
逐個走訪包括 head 在內的 entry 並將其釋放
原先未考慮到 element 內還有 value 要釋放而出錯,並且在輸入值為 NULL 也沒有進行例外處理,因此修改為
判斷佇列是否存在,否則回傳 false ,是則向記憶體索取大小為 element_t 的空間,失敗則回傳 false ,成功則將 s 的內容複製到節點內,並將該節點放入 queue 的開頭並回傳 true 。
new 配置時的 new->value 尚未配置記憶體空間,因此 strcpy
會報錯,因此需要先行計算欲放入的字串大小並向記憶體索取適當的空間,然後再用 strncpy
將字串放入。
註 :
strcpy
與strncpy
的差別在於strcpy
會有 buffer overflow 的問題,因為其不考慮 *dest 是否有足夠空間放下 *src 的字串,而strncpy
則會透過最後一個參數 count 來限制,但在必要時需自行補上\0
來做結尾。 參考來源
大致與 q_insert_head 相同,僅節點放置之位置不同。
同 q_insert_head 做修改
將 queue 中的第一個節點移出 queue 並回傳該節點,同時將節點中的內容複製到 sp 中,若queue 不存在或為空時回傳 NULL 。
題目上表示 if sp is non-NULL… ,因此加上對 sp 的判斷式。此外,之前對strncpy有誤會,字串末的 \0
是要自己加的,連同最後第二行錯誤也一齊修正,修改後如下:
大致同 q_remove_head
。
同 q_remove_head 做修改
利用一個 counter 逐個計算 queue 中的節點個數(不包含 head ),當 queue 不存在時回傳0
利用快慢指標來找出位在中間的節點,因作業要求是在 queue size 為偶數時刪除第 個,因此要從 head 開始,而非從 head->next 開始,刪除時先利用 list_del_init
將位處中間的節點 slow 從 queue 中移除,再用 q_free 將該節點的記憶體釋放。
註 :
list_del
不會將該節點的 prev, next 指回自身,因此直接用使用 q_free 會有問題,需要先取得整個節點( element_t )後,再進行 free 。
先前撰寫時錯以 singly linked list 的方式來寫,但這樣在 doubly linked list 中會有 infinite loop 的問題發生,因此改成以個數計算的方式來做為迴圈的條件,找出第 個節點
將佇列裡有重複出現的所有字串刪除,當佇列不存在或為空時回傳 false ,若只有一個節點則因不會有重複出現直接回傳 true 。
將佇列中的節點兩兩交換位置
使用 List API 改寫為更精簡好讀的程式碼。
先前對 list_add 有些誤會,仔細想一想才發覺改成這樣做是沒問題的,雖然以指標操作的數量來說並沒有減少,但相比於上方的程式碼來說,更為直觀且好讀。
將佇列中的節點順序反過來
將佇列中節點依照字串值由小至大排序
以上是用 merge sort 實作,理論上時間複雜度為 ,而以測試的結果來說,該 sort 實作可以通過第 15 項測試,表示其時間複雜度確實在 ,然而此 sort 實作在第 14 項測試中會失敗。
接著引入 lib/list_sort.c
從 list_sort.c
改寫的 sort 實作可通過第 14 項測試,觀察兩種 sort function 在 perf top
中的使用情況,主要由兩項函式消耗大部分的資源,第一個是 sort 本身,第二個是 strcmp
,前後兩個相比 strcmp
消耗程度大致相同,因此合理推斷導致第 14 項測試結果出現分歧的原因,就是第一個 sort 實作使用的是 recuresive 的結構,而使得資料量大的時候會出現崩潰。
需要更多分析和實驗,來支持你的論點。
利用 valgraind ./qtest
,並執行如 14 項測試中的命令順序 :
new
-> ih dolphin 1000000
-> it gerbil 1000000
-> reverse
-> sort
其中用 recursive 形式的 sort 實作會跑出以上的訊息,並以 Segmentation fault (core sumped)
的錯誤結束程式。
仔細看看錯誤訊息,可以發現出錯的原因是主程式 main 的 stack 沒有空間了,也就是大名鼎鼎的 stack overflow,這是因為函式在執行的時候,若是遇到要執行其他函式,則會把目前的參數值、變數以及其他函數結束後的返回位址 push 到該函式的 stack 中,直到要繼續執行才會 pop 出來,而也就是因為這個特性,使得 recursive 形式的函式雖然直觀但執行速度慢 (push, pop 都要花時間),而且還有發生 stack overflow 的風險。
將佇列中的節點打亂,演算法參考
以下是直接利用演算法的原理實作的,能夠運作但依然需要改進,尤其是面對巨量資料的時候。
因為 queue.h
不能更動的關係,需要額外寫一個 .h
檔來放置函式的宣告,如下,並在 qtest.c
中將其 include 進去
原本的演算法中,面對結構簡單的陣列可以達到 的時間複雜度,但在結構較複雜且記憶體不連續的 linked list 中,要還原演算法的話就必須花費 的時間複雜度在逐個尋找節點的過程上。
總是書寫為 linked list,中間不要有連字號。
而若不考慮完全按照演算法,不用交換 node 的方式來做,則可以不使用 swap 函式,改成如下,在指標的操作上次數會固定,而不需要進行條件判斷
完全移除 swap_two_node
,改為用 list api 來實作
使用 tiny-web-server 提供伺服器功能,目前如下的程式能符合伺服器運作的同時, qtest
仍可輸入指令 命令的要求,但是當 tiny
有輸出訊息時,畫面會被覆蓋掉,不影響運作。
instruction 是「指令」,command 是「命令」,二者語意不同。
更新錯誤 : 當
qtest
結束執行時,tiny
程式不會結束執行,也無法用jobs
命令查到。
不要呼叫 system
函式,你應該引入 tiny-web-server 的原始程式碼並整合。
首先先看看 select 的用途,參考了這篇以及這篇,了解 select 中各項傳入參數所代表的意義。
select(nfds, readfds, writefds, exceptfds, timeout)
中, ndfs 用以表示可以檢查的 file descriptors 的範圍,中間三個參數個別表示指向存放 read, write, except 的三個 file descriptors set (fd_set) ,最後 timeout 參數則限制了 select
要等待多久,特別的是當 timeout 值為 NULL 時, select
會無限制的等下去。
當 select 回傳 -1 表示有錯誤發生,回傳 0 表示等待超時,而成功時則會回傳一個值,根據描述,這個值是大於0的,端看傳入的 fd_set 大小。
the total number of bits that are set in readfds, writefds, exceptfds
回頭看 select 在程式中在何處使用,是在 console.c 的 cmd_select
中,並且依據 cmd_select
的使用, nfds, writefds, exceptfds, timeout 四個參數的傳入值都是固定的,分別是 0 以及三個 NULL ,這意味著 select
永遠只會看 read 的 fd_set 中的第一個,而且會無限制地等到該 fd 準備好,而該 fd 就是指向我們所輸入的命令ㄉ,或是 source
從檔案中讀到的第一個命令。
如果輸入 make valgrind
來檢查程式運作,在將 queue.c 撰寫完成之後,仍可看到來自 linenoise.c 的錯誤發生,而主要是從 linenoiseHistoryAdd 的函式中所取的記憶體沒有完全釋放掉,而使部分資料仍 'reachable'
先看這部分的程式碼是如何運作
這個函式看起來沒有問題,因此錯誤應該不是發生在函式內部,接著看發生 memory leak 的地方是在 qtest.c 呼叫 history load 時所產生的,推測是結束時 histroy 的記憶體沒有被釋放掉,所以再來看 main 結束之前的動作,
TODO