contributed by < chiehen
>
linux2021
在程式中 queue.h 定義了鏈結串列(linked-list) 的結構:
為單向的 linked-list,
接著用鏈結串列來實作佇列 (queue):
可發現 queue.c 僅提供介面 (interface) 但尚未有完整程式實作,包含以下:
q_new
: 建立新的「空」佇列;q_free
: 釋放佇列所佔用的記憶體;q_insert_head
: 在佇列開頭 (head) 加入 (insert) 給定的新元素 (以 LIFO 準則);q_insert_tail
: 在佇列尾端 (tail) 加入 (insert) 給定的新元素 (以 FIFO 準則);q_remove_head
: 在佇列開頭 (head) 移去 (remove) 給定的元素。q_size
: 計算佇列中的元素數量。q_reverse
: 以反向順序重新排列鏈結串列,該函式不該配置或釋放任何鏈結串列元素,換言之,它只能重排已經存在的元素;q_sort
: 以遞增順序來排序鏈結串列的元素。因此第一個作業要求即是完成此部份實做。
根據註解創建一個空的 queue, 將 head
及tail
指向 NULL
, size
設為 0
。如果無法成功獲得動態配置的記憶體空間則回傳 NULL
。
以下為程式碼:
當呼叫此函式時將釋放 queue 中所有配置的記憶體空間,依照 linked-list 單向的結構,從 head
開始逐一釋放記憶體。 另外,因 list element 中成員 value
有額外配置記憶體,因此在釋放記憶體時,須先釋放 value
的記憶體。在釋放完所有 linked-list 的記憶體後,最後釋放 queue 的記憶體。
以下為程式碼:
此段函式在佇列開頭 (head) 加入 (insert) 給定的新元素。依據註解如果 queue 是NULL
,或記憶體配置失敗則回傳 false
。
配置出字串的記憶體時:
可注意到配置的空間是 strlen(s) +1
bytes, 是因為根據 Linux Programmer's Manual:
The strlen() function calculates the length of the string pointed to by s, excluding the terminating null byte ('\0').
但在複製字串時也須複製空字元 ('\0') 因此多配置1 byte 儲存此字元,
此外, 須注意若此時字串配置記憶體失敗, 在回傳 false
前要記得釋放先前配置的 list element 記憶體, 以免記憶體漏失(memory leak)。
一開始在複製字串時使用 strcpy()
, 但在執行 git commit 時Git pre-commit hook 提示 strcpy()
為不安全函式,
本來想改為使用較完善且安全的 strlcpy
, 但發現此函式並沒有被 glibc 支援, 因此改用 strncpy()
複製字串:
完整程式碼為:
在佇列尾端 (tail) 加入 (insert) 給定的新元素。依據註解如果 queue 是NULL
,或記憶體配置失敗則回傳 false
。
為達成運算時間為 的要求, 增加新成員 list_ele_t *tail
至 queue 的結構以紀錄尾端位置:
實做方式與 q_insert_head
大致相同, 只有在更新 queue 時須改為以下:
完整程式碼:
在佇列開頭 (head) 移去 (remove) 給定的元素, 並將被移除字串複製至 *sp
。
buffsize
的情形:此時將只複製 bufsize-1
characters
q_insert_tail
時使用到已釋放的記憶體, i.e.因此須添加判斷:
完整程式碼:
計算佇列中的元素數量, 且運算時間須為 。
為達成 要求, 須增加新成員 int size
至 queue 的結構:
建立新的佇列時(q_new
) 將 size
初始至 。在函式 q_insert_head
, q_insert_tail
, q_remove_head
中須更新 size
。
則在 q_size
只須回傳 size
即可:
函式以反向順序重新排列鏈結串列。
當更新 next
前, 須紀錄前一個元素(q->head)位置及後一個元素位置(n)。
以遞增順序來排序鏈結串列的元素。
參考Big-O Cheat Sheet, 選擇 Average case 和 Worst case 表現都不差 () 的 Merge sort 實做 q_sort。
參考 Linked List Sort 實做 Merge Sort
在 Merge Sort 中須尋找 list 的中間點。 利用兩變數 fast
和 slow
, fast
速度為每次兩步, slow
速度為每次一步, 因此當 fast
走至 list 尾端時(), slow
便在 list 中間():
而在 merge
函式中, 原先如參考文件一樣建立 pseudo node 來進行合併:
但在使用 qtest 測試時得到報錯:
因此得知不允許在此處要求配置記憶體,因此改為先比較兩個 list 的第一個元素:
Merge Sort 完整程式碼:
測試結果:
開啟 Address Sanitizer,修正 qtest 執行過程中的錯誤。
開啟 Address Sanitizer, 如果先前已生成執行檔, 先清除:
執行測試:
錯誤訊息節錄:
得知錯誤發生在 console.c 的 369 行
又與變數 'simulation' 有關
發現在 104 行, simulation 從 bool 型態被強制轉形成 int 型態後被放入 param_ptr 結構的成員 valp 中
因此在369行取值時發生錯誤
嘗試宣告 simulation 為 int
報錯:
也更改 consle.h 中的宣告
另外發現執行 qtest 中的 help 指令也會有錯誤訊息
錯誤訊息節錄:
發現一樣在 108 行時被強制轉型
同理將 echo 也宣告為 int 型態
運用 Valgrind 排除 qtest 實作的記憶體錯誤,並透過 Massif 視覺化 “simulation” 過程中的記憶體使用量,需要設計對應的實驗
得到輸出:
發現造成記憶體錯誤的為引入的套件 linenoise 中
使用 massif 視覺化
得到: