contributed by < r34796Kevin >
實作Jserv老師的Linux 核心設計 (Linux Kernel Internals)作業1,並且盡量完成作業的額外要求
,練習開發程式的好用工具,包含:
本次作業主要實作queue並實現以下功能:
下面的程式碼會依據時間呈現從最初版本到最終版本還有紀錄下開發中遇到的bug。
我們使用的資料型態長這樣
為了使 insert_tai() 和 size() 兩函式都在 O(1) 的時間複雜度,我們在queue.h增加兩個 Object, tail 和 size。
完成一些基本的實作
為pointer q配置一個記憶體位置,初始化參數後返回記憶體位置。
原本的版本,忘記處理queue_t *q = NULL
的情況,
所以在自動評分程式中會有dereferenced a NULL or invalid pointer的提示。
–- Trace Points
+++ TESTING trace trace-01-ops:
Test of insert_head and remove_head
Segmentation fault occurred. You dereferenced a NULL or invalid pointer
–- trace-01-ops 0/6
使用Valgrind: 動態程式分析工具,發現new、insert_head、remove head
都沒有問題,在使用free
的時候會dereference一個NULL pointer。
$ valgrind -q –leak-check=full ./qtest
cmd> new
q = []
cmd> ih "a"
q = ["a"]
cmd> ih "b"
q = ["b" "a"]
cmd> ih "c"
q = ["c" "b" "a"]
cmd> rh
Removed "c" from queue
q = ["b" "a"]
cmd> rh
Removed "b" from queue
q = ["a"]
cmd> rh
Removed "a" from queue
q = []
cmd> free
8251 Invalid read of size 8
8251 at 0x10E2E0: q_free (queue.c:37)
8251 by 0x10ADA2: do_free (qtest.c:150)
8251 by 0x10CE65: interpret_cmda (console.c:221)
8251 by 0x10D2FC: interpret_cmd (console.c:244)
8251 by 0x10DDA9: run_console (console.c:660)
8251 by 0x10C2BF: main (qtest.c:788)
8251 Address 0x0 is not stack'd, malloc'd or (recently) free'd
修改後的function如下
釋放佇列所佔用的記憶體,注意需要分別釋放struct ELE中為Value分配的記憶體空間,為了避免dereference NULL pointer所以需要if (pre != NULL)
的判斷。
在佇列開頭加入給定的新元素,如果傳入的指標是NULL則回傳false,如果分配記憶體空間失敗也要回傳false,記得為Value分配大小為length * sizeof(char)
的記憶體空間並用snprintf(newh->value, length, "%s", s)
賦值。
操作與bool q_insert_head(queue_t *q, char *s)
相似,注意指標操作。
在佇列開頭移去給定的元素。根據Jserv老師的說明:
“remove” 這動作對於 linked list 來說,是斷開節點和節點的關聯,也就是說原本若是 A → B → C,在 remove(B) 操作完成後,就會變成 A →
C 的節點關係。特別注意到 B 這個節點其實還存在於記憶體中,只是我們無法從節點間的關係看出來。於是 “remove” 可解讀為 “take away” (將某物帶走)
“delete” 則有 “erase” 的涵義,若用於 linked list,則指某個節點將被「抹除」,對應的記憶體就會有變化
應該不用從記憶體中刪除head的記憶體。多做了free(remove->value); free(remove);
的動作。
計算佇列中的元素數量,因為每次新增跟移除節點的時候都有更新q->size
,故可以O(1)時間完成。
原本的版本,忘記處理struct ELE* q->head = NULL
的情況,而且struct ELE *tmp = cur;
指標指錯應為struct ELE *tmp = cur->next;
+++ TESTING trace trace-03-ops:
Test of insert_head, insert_tail, reverse, and remove_head
ERROR: Time limit exceeded. Either you are in an infinite loop, or your code is too inefficient
ERROR: Time limit exceeded. Either you are in an infinite loop, or your code is too inefficient
ERROR: Time limit exceeded. Either you are in an infinite loop, or your code is too inefficient
ERROR: Removed value gerbil != expected value squirrel
ERROR: Time limit exceeded. Either you are in an infinite loop, or your code is too inefficient
Error limit exceeded. Stopping command execution
–- trace-03-ops 0/6
修改後
以反向順序重新排列鏈結串列,該函式不該配置或釋放任何鏈結串列元素,換言之,它只能重排已經存在的元素。使用三個指標pre、cur、tmp
紀錄下Node的位置,避免更動next
後後面的Node記憶體位置遺失。
以遞增順序來排序鏈結串列的元素,這邊使用以前寫過的Linked list Mergesort來改成可以對character做排序,做完mergesort後更改head與tail指標。
做mergesort時,先用fast與slow兩個指標找出中間的Node,用struct ELE *head2 = (slow->next); slow->next = NULL;
分成兩條Linked list,再遞迴呼叫mergesort直到剩下一個Node,然後呼叫struct ELE* merge(struct ELE *l, struct ELE *r)
做排序。
struct ELE* merge(struct ELE *l, struct ELE *r)
可以將兩段排序好的Linked list合併成一條Linked list,並且返回新的head。其中用了一個dummy node作為Linked list的起點,因此返回新的head之前需要先刪除dummy node才不會造成memory leak。
while迴圈中分別判斷struct ELE *l
還是struct ELE *r
的value
比較小,直到其中之一指向NULL
。然後把剩下的Linked list接上去。
修改完所有的bug後執行自動評分程式,獲得滿分。
–- TOTAL 100/100
本次作業算是滿基礎的作業,以前有做過類似的C++作業,但是額外加上了一些malloc失敗的判斷,還有memory leak的檢查,還要使用Cppcheck: 靜態程式碼分析工具、Valgrind: 動態程式分析工具與github,以及第一次使用Linux開發,所以還是花了很多時間學到了很多東西。