contributed by < justapig9020
>
sysprog2020
queue.[ch]
和連帶的檔案,測試後用 Git 管理各項修改,記得也該實作 q_sort
函式。
qtest
實作的記憶體錯誤,並透過 Massif 視覺化 "simulation" 過程中的記憶體使用量,需要設計對應的在結構部分,於 queue_t
中新增兩成員, tail
、 size
。
上述兩成員變數將會在後續用到的時候討論。
在使用 struct
的成員變數此一用詞時,突然發現自己只能肯定此用法在 cpp
class
的狀況下正確,然而並不肯定可以使用於 c
struct
的情況下。
既然有問題只好學學大文豪查字典了。
以下節錄自 c99 commit draft
a member of a structure, union, or enumeration
6.2.1
A structure or union shall not contain a member with incomplete or function type
6.7.2.1
由於與 staruct
相關章節中多次使用 member 一詞,因此認定
本 function 實作只需考慮 queue
malloc 失敗的情況。
根據 男人 , On error, these functions return NULL.
, malloc
再失敗時會返回 NULL
,透過判斷回傳值來迴避失敗的情況。
其餘的部分依照題目需求來初始化數值,其中為了滿足 size
以及 insert tail
O(1) 的要求,新增變數 size
、 tail
,兩者分別紀錄 queue
當前節點個數以及 指向最後一個節點的指標的指標
,亦即指向倒數第二節點 next
成員的指標。
關於 tail
的設計思考,未來有時間再新增篇幅討論。
依據理解以及與友人的討論得出 - 為避免資安上 uaf
以及 double free
的風險,被 free() 的指標應該被立即 assign 成 NULL
的結論。
然而在 free(q);
後加上 q = NULL;
則會導致 pre-commit
失敗。
首先檢查 q
是否存在,若存在則利用 q->head
作為 buffer 將整個 queue
從頭清空。
其中 list_ele_t
中的成員 value
是指向 char
的指標,須在 free
整個節點前先行將其 free
。
在實作此函數時重新思考了自己對 malloc
、 free
的認知。主要思考議題如下。
malloc
嘗試分配 size 為 0
的空間時行為為何。free
上述空間時會發生什麼狀況。根據男人敘述 If size is 0, then malloc() returns either NULL, or a unique pointer value that can later be successfully passed to free().
可得知上述兩問題之解答。
malloc
基本上會回傳 NULL。free
。malloc
時需要考慮回傳為 NULL
的情況,以及在有可能 malloc
size 為 0 的空間時,要注意不要在 size 為 0 的情況下讀寫該記憶體位址。透過在 queue_t
中新增成員變數 size
來紀錄當前結點個數,以達成 O(1) 之複雜度要求。
每逢新增刪除都需要對應的加減 size
。
首先考量之後還有其他 insert 系列 function 因此實作了一個靜態函數 new_ele
來提昇代碼的重用性。
判斷各個 malloc
的成功與否,並透過 goto
的方式構成一個類似於 try catch
的架構。實作此架構是為了確保任意的錯誤都能在返回前妥善的處理已經取用的資源。
此外需要注意的是,根據男人所述, The strlen() function calculates the length of the string pointed to by s, excluding the terminating null byte ('\0').
, strlen 並不會包含 '\0' 因此在記憶體分配上需要將此數值加一以確保獲得的記憶體有足夠的空間保存 '\0' 。
透過 new_ele
函數生成新節點,透過回傳值判斷新節點是否有成功建立。
將新節點插入 queue 的 head
,並考慮當前 queue 為空時,要設定 tail
指向當前節點的成員變數 next
。
重複使用 new_ele
新增節點,由於 tail
為指向最後一個節點的 next
的指標,因此在 insert tail 的操作不用將 queue
為空時視為特殊狀況,可以直接更新。
使用 prev
ptr
next
三個指標進行節點反轉作業。
prev
用以保存前一個指標,在反轉後他將會成為 ptr
節點的 next
。
ptr
指向當前操作的節點。
next
則指向原本的下一個節點,以確保 ptr
在其成員變數 next
更新後仍可以順利前往原本的下一節點。
首先再實作 sort
前,先實作 half
來找出 queue
的中間點 (實際上為 )。
藉由 2:1 的快慢指標找出 queue 的中間節點。
find_mid_ele
考量到後續操作實際上同時也需要改變 的 next
, 因此返回值為指標的指標。
在實作時一度困惑於如何決定排序之順序,最後參考 ZhuMon 的開發紀錄才發現是使用 strcmp
作為排序依據。在文件上是否有需要對於排序依據有更清楚的指示?
Sort 的實現參考 linked list sort 使用 merge sort
作為排序演算法,並拆分成三個部分進行。
首先透過 find_mid_ele
找到指向中間節點的指標的指標。透鍋該指標可以便利的找出後半截 queue
的起始節點位址以及改動前半截 queue
的最後一個 next
為 NULL
以分割 queue
。
如同常規的 merge sort
藉由遞迴的方式將 queue
分割至最小單位,並依照大小逐一重組。
透過參考 ZhuMon 的實作,改進忽略 linked list 特性,而歷遍已經串好的串列。並且修復 tail
指向錯誤位址的 bug 。
首先透過 Valgrind
逐一測試 trace
的測試腳本,發現以下腳本會發生警告。
發生 memory leak 。
發生 memory leak 。
Valgrind 發生 core dumped
。
透過 masasif
產生 log 並藉由 massif-visualizer
視覺化,產生以下圖表(下圖為 trace-13
所產生的圖表)。
比對圖表與腳本後可以發現,在程式結束前,有大量的記憶體未被施放。而這歇腳本的共通點皆為未在最後執行 free
命令。
原本推測是 qtest
中並未在結束前檢查並施放記憶體,然而發現實際上在 main
裡就會透過 add_quit_helper
將 queue_quit
(其中會引用 q_free() ) 存入 function pointer array quit_helpers
,最終在處理 quit
指令會依序執行 quit_helper
中的函數。