# 2024q1 Homework1 (lab0) contributed by < `zoo868e` > ## 開發環境 ```shell $ uname -a Linux mattjan 5.15.0-92-generic #102-Ubuntu SMP Wed Jan 10 09:33:48 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux $ gcc --version gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 ``` ## 指定佇列的操作 通過 `make test` 的所有測試。 :::danger 無論標題和內文中,中文和英文字元之間要有空白字元 (對排版和文字搜尋有利) ::: 由於作業中操作的鏈結串列已經實作了一些功能,所以我先了解了 `list.h` 中的所有功能。 :::danger 避免過多的中英文混用,已有明確翻譯詞彙者,例如「鏈結串列」(linked list) 和「佇列」(queue),就使用該中文詞彙,英文則留給變數名稱、人名,或者缺乏通用翻譯詞彙的場景。 ::: ### list utilities in `list.h` - [Declaration, initialization, and common utilities](#Declaration-initialization-and-common-utilities) - [Add node into list](#Add-node-into-list) - [Remove the node from the list](#Remove-the-node-from-the-list) - [The status of list](#The-status-of-list) - [Add list nodes from a list to another](#Add-list-nodes-from-a-list-to-another) - [Move list nodes from a list to another](#Move-list-nodes-from-a-list-to-another) - [Get the entry by list node](#Get-the-entry-by-list-node) - [Iterate the list](#Iterate-the-list) #### Declaration, initialization, and common utilities :::danger 避免非必要的項目縮排 (即 `* ` 和 `- `),以清晰、明確,且流暢的漢語書寫。 ::: `list_head`: 定義`list_head`結構。這個結構包含兩個指標`prev`和`next`,分別指向上一個和下一個節點,以形成雙向環狀鏈結串列。 ```c! struct list_head { struct list_head *prev; struct list_head *next; } ``` `container_of`:獲取 `type` 的起始地址。`type` 包含 `ptr` 所指向的物件。 ```c! #ifndef container_of #ifdef __LIST_HAVE_TYPEOF #define container_of(ptr, type, member) \ __extension__({ \ const __typeof__(((type *) 0)->member) *__pmember = (ptr); \ (type *) ((char *) __pmember - offsetof(type, member)); \ }) #else #define container_of(ptr, type, member) \ ((type *) ((char *) (ptr) -offsetof(type, member))) #endif #endif ``` `LIST_HEAD`: 宣告一個新的 `list_head`,並將其初始化為空佇列。 ```c! #define LIST_HEAD(head) struct list_head head = {&(head), &(head)} ``` `INIT_LIST_HEAD`: 將傳入的指標所指向的物件內容初始化為空的鏈結串列。 ```c! static inline void INIT_LIST_HEAD(struct list_head *head) { head->next = head; head->prev = head; } ``` #### Add node into list :::danger 無論標題和內文中,中文和英文字元之間要有空白字元 (對排版和文字搜尋有利) ::: `list_add`: 在鏈結串列的起點新增一個新的節點 ```c! static inline void list_add(struct list_head *node, struct list_head *head) { struct list_head *next = head->next; next->prev = node; node->next = next; node->prev = head; head->next = node; } ``` `list_add_tail`: 在鏈結串列的尾端新增一個新的節點 ```c! static inline void list_add_tail(struct list_head *node, struct list_head *head) { struct list_head *prev = head->prev; prev->next = node; node->next = head; node->prev = prev; head->prev = node; } ``` #### Remove the node from the list :::danger 改進你的漢語表達 ::: `list_del`: 從鏈結串列中移除節點。在註解中,老師詳細說明了 Remove 與 Delete 的不同之處。雖然這個功能使用 `del` 命名,但實際上的行為是 Remove,因為它操作結束後,節點仍然存在,並沒有被刪除。 ```c! static inline void list_del(struct list_head *node) { struct list_head *next = node->next; struct list_head *prev = node->prev; next->prev = prev; prev->next = next; #ifdef LIST_POISONING node->prev = (struct list_head *) (0x00100100); node->next = (struct list_head *) (0x00200200); #endif } ``` `list_del_init`: 與 `list_del` 相同的操作,只是會重設移除的節點所指向的前一個與下一個節點,以免造成使用者可以透過這個節點非法訪問其他節點。 ```c! static inline void list_del_init(struct list_head *node) { list_del(node); INIT_LIST_HEAD(node); } ``` #### The status of list `list_empty`: 返回鏈結串列是否為空。 ```c! static inline int list_empty(const struct list_head *head) { return (head->next == head); } ``` `list_is_singular`: 回傳鏈結串列中除了開頭以外,是否只有一個節點。 ```c! static inline int list_is_singular(const struct list_head *head) { return (!list_empty(head) && head->prev == head->next); } ``` #### Add list nodes from a list to another `list_splice`:將鏈結串列`list`中的所有節點都添加到`head`的開頭,是複數版本的`list_add`。 ```c! static inline void list_splice(struct list_head *list, struct list_head *head) { struct list_head *head_first = head->next; struct list_head *list_first = list->next; struct list_head *list_last = list->prev; if (list_empty(list)) return; head->next = list_first; list_first->prev = head; list_last->next = head_first; head_first->prev = list_last; } ``` `list_splice_tail`: 將鏈結串列`list`中的所有節點都添加到`head`的尾端,是複數版本的`list_add_tail`。 ```c! static inline void list_splice_tail(struct list_head *list, struct list_head *head) { struct list_head *head_last = head->prev; struct list_head *list_first = list->next; struct list_head *list_last = list->prev; if (list_empty(list)) return; head->prev = list_last; list_last->next = head; list_first->prev = head_last; head_last->next = list_first; } ``` #### Move list nodes from a list to another `list_splice_init`: 功能與 `list_splice` 相同,但會重新初始化 `list`,以防止使用者透過此節點非法訪問其他節點。 ```c! static inline void list_splice_init(struct list_head *list, struct list_head *head) { list_splice(list, head); INIT_LIST_HEAD(list); } ``` `list_splice_tail_init`:與 `list_splice_init` 功能類似,不同之處在於將節點們添加到尾端。 ```c! static inline void list_splice_tail_init(struct list_head *list, struct list_head *head) { list_splice_tail(list, head); INIT_LIST_HEAD(list); } ``` `list_cut_position`: 將`head_from`的下一個節點包含`node`切割出來,再將`head_to`指向切割出來的鏈結串列。需要注意的是,`head_to`將等於被切割出來的鏈結串列,而不是將其新增到原始鏈結串列中。 ```c! static inline void list_cut_position(struct list_head *head_to, struct list_head *head_from, struct list_head *node) { struct list_head *head_from_first = head_from->next; if (list_empty(head_from)) return; if (head_from == node) { INIT_LIST_HEAD(head_to); return; } head_from->next = node->next; head_from->next->prev = head_from; head_to->prev = node; node->next = head_to; head_to->next = head_from_first; head_to->next->prev = head_to; } ``` `list_move`: 將`node`從原本的鏈結串列中移動到指定的鏈結串列的開頭。 ```c! static inline void list_move(struct list_head *node, struct list_head *head) { list_del(node); list_add(node, head); } ``` - `list_move_tail`: 將`node`從原本的鏈結串列中移動到指定的鏈結串列的末端 ```c! static inline void list_move_tail(struct list_head *node, struct list_head *head) { list_del(node); list_add_tail(node, head); } ``` #### Get the entry by list node `list_entry`: 取得 `type` 的起始地址,`type` 的成員包括 `node` 所指的物件。 ```c! #define list_entry(node, type, member) container_of(node, type, member) ``` `list_first_entry`: 取得鏈結串列中的第一個 `type` 的起始位址 ```c! #define list_first_entry(head, type, member) \ list_entry((head)->next, type, member) ``` `list_first_entry`: 取得鏈結串列中的最後一個 `type` 的起始位址 ```c! #define list_last_entry(head, type, member) \ list_entry((head)->prev, type, member) ``` #### Iterate the `list` :::danger - [ ] traverse (動詞) 和 traversal (名詞) 根據 Dictionary.com 的[解釋](https://www.dictionary.com/browse/traverse ): (作為及物動詞和不及物動詞都有類似的意思,以下列出作為及物動詞的寓意) * to pass or move over, along, or through. * to go to and fro over or along. 其實這意思很好懂,就像我們「走過」/「穿越」校園一般,於是 traverse a linked list 就會是「(用某種手段) 存取多個鏈結串列的節點」,但這裡卻沒有必要「所有」的範圍:英語的 "move over/through" 用於某個區域時,根本沒有這樣的隱含意義。如果將 traverse 翻譯為「遍歷」,就會導致「超譯」,也就是跳脫「直譯」和「意譯」。 當我們回頭看 "traverse" 所在的技術描述內容,例如 "traverse every node",若翻譯為「遍歷每個節點」,那麼既然都「遍」(意即「全面」、「到處」),又何來「每個」節點呢?於是,合理的翻譯應改為「逐一走訪每個節點」 —— 差異在哪?在 "traverse every node" 的應用場景中,可能是我們嘗試在鏈結串列尋找特定的節點內含的資料,一旦找到就停止,或者我們要偵測給定的鏈結串列是否包含環狀 (circular) 結構 ,並沒有真的要「遍」(到處/全面)「歷」(意即「經過」) 每個節點。在我們的用語中,要區分「意圖」(intention) 和「實際作用」(reaction),濫用「遍歷」會使得語意不清,從而難以推測英語原文的訴求。 還有個更重要的原因是,「遍歷」這詞已在理工領域存在,且廣泛使用,即「遍歷理論」(Ergodic theory),後者是研究具有不變測度 (invariant measure) 的動力系統及其相關問題的一個數學分支。 遍歷理論研究遍歷變換,由試圖證明統計物理中的遍歷假設 (Ergodic hypothesis) 演進而來。 在統計學中,若單一個物理系統在不同時間內重複相同的實驗 (如丟擲一枚硬幣),其取樣數據所得的統計結果 (如硬幣出現正面的機率) 和極多個完全相同的物理系統的系集 (如丟極多個相同的硬幣) 在同時作相同的實驗取樣數據的統計結果假設為相同時,此種假設即稱為「遍歷性假設」或「遍歷假設」。基於這個假設,對時間平均的統計方式及結果,便可由對系集的平均及統計方式得到。在一般物理系統中,尤其在統計力學範圖中,均採用此遍歷性假設為基本的假設。在流體力學中對亂流的實驗分析,亦是採用此假設。 遍歷 (Ergodic) 源於以下二個希臘詞: * ergon (對應英語的 work) * odos (對應英語的 path 或 way) 最初這是由奧地利物理學家波茲曼 ([Ludwig Boltzmann](https://en.wikipedia.org/wiki/Ludwig_Boltzmann)) 於統計力學領域 (statistical mechanics) 提出的概念,其一廣為人知的結果是:在經過長時間後,時間平均將會趨近空間平均。此事實在動力系統扮演極重要的角色,在隨機分析領域亦然。 因此,若貿然將 traverse 翻譯為「遍歷」,一來語意不清,二來增加和理工多項領域專業人員的溝通成本,實在得不償失。 $\to$ [資訊科技詞彙翻譯](https://hackmd.io/@sysprog/it-vocabulary) ::: `list_for_each`: 逐一走訪鏈結串列中的每個節點。在這個過程中,如果更改節點指向下一個節點的指標,可能會導致多次走訪同一節點或者跳過節點。 ```c! #define list_for_each(node, head) \ for (node = (head)->next; node != (head); node = node->next) ``` `list_for_each_entry`: 逐一遍歷鏈結串列中的每個節點,並且`entry`會指向包含當前節點的物件。同樣的,途中若修改物件所包含的節點,也可能導致多次遍歷同一節點或者跳過節點。 ```c! #ifdef __LIST_HAVE_TYPEOF #define list_for_each_entry(entry, head, member) \ for (entry = list_entry((head)->next, __typeof__(*entry), member); \ &entry->member != (head); \ entry = list_entry(entry->member.next, __typeof__(*entry), member)) #endif ``` `list_for_each_safe`: 逐一遍歷鏈結串列中的每個節點,期間會使用變數`safe`保留當前節點的下一個節點位址,這樣使用者可以對當前節點進行任意操作,而不會造成問題。 ```c! #define list_for_each_safe(node, safe, head) \ for (node = (head)->next, safe = node->next; node != (head); \ node = safe, safe = node->next) ``` `list_for_each_entry_safe`:同時具有 `list_for_each_entry` 和 `list_for_each_safe` 的功能,用於逐一遍歷鏈結串列中的每個節點,並且 `entry` 會指向包含當前節點的物件。期間會使用變數 `safe` 保留當前節點的下一個節點位址,這樣使用者可以對 `entry` 進行任意操作,而不會造成問題。 ```c! #define list_for_each_entry_safe(entry, safe, head, member) \ for (entry = list_entry((head)->next, __typeof__(*entry), member), \ safe = list_entry(entry->member.next, __typeof__(*entry), member); \ &entry->member != (head); entry = safe, \ safe = list_entry(safe->member.next, __typeof__(*entry), member)) #undef __LIST_HAVE_TYPEOF #ifdef __cplusplus } #endif ``` ### 指定佇列的操作 - `q_new`: 配置記憶體並初始化,失敗時回傳 `NULL` 。 - `q_free`: 使用 `list_for_each_entry_safe` 走訪佇列中的每一個物件,將每個物件的內容透過 `q_release_element` 釋放,最後再使用 `free` 釋放 `head` 節點佔據的記憶體。 - `q_release_element`: 原本就已經實作好的方法,會使用 `test_free` 釋放物件中 `value` 指向的空間以及將 `list` 從鏈結串列中移除 - `q_insert_head`, `q_insert_tail`: 在佇列中新增物件到起點/尾端,由於這兩個方法都需要建立新的物件,所以實作了 `q_new_element` 。這兩個方法的差異在於插入時的位置,可以分別使用` list_add` 以及 `list_add_tail` 兩個功能插入 - `q_new_element`: 原先使用 `strlen` 算出字串長度,然後用 `malloc` 配置包含 `'\0'` 的記憶體空間,再使用 `memcpy` 複製內容到配置的空間中,但是這樣的方法遍歷了兩次 `s` ,所以後來改用`strdup`將內容複製出來。 - `q_remove_head`, `q_remove_tail`: 將物件從佇列中移除,並且必要時複製出物件的 `value` 。原先使用 `memcpy` 將 `value` 複製 `bufsize` 個內容到輸出 `sp` 中,但是透過 `make valgrind` 發現這樣的方法沒有考慮到原先 `value` 中的長度,因此改為使用 `strncpy` 。 ```c - if (ret && sp && bufsize > 0) { - memcpy(sp, ret->value, bufsize - 1); + if (sp && bufsize - 1 > 0) { + strncpy(sp, ret->value, bufsize - 1); ``` - `q_size`: 用 `list_for_each` 走訪所有節點,算出佇列的節點個數。 - `q_delete_mid`: 用兩個指標分別代表快跟慢的移動,所以當移動較快的指標走訪到 `head` 節點或者走到已經走過的節點時,代表較慢的指標所指向的節點為要刪除的節點。 - `q_delete_dup`: 由於這個功能預設輸入為已排序佇列,所以只要依序走訪所有節點,就可以很簡單的將所有重複 `value` 的物件刪除。 - `q_reverse`: 透過 `list_for_each_safe` 把每個物件用 `list_move` 搬移到 `head` 的開頭。 - `q_reverseK`: 將每個節點搬移到一個暫存的鏈結串列(`tmp`)的開頭,再使用 `list_splice_init` 將暫存的節點們搬回原本的list中。若 `tmp` 的長度不足 `k` ,則會先使用 `q_reverse` 將內容反轉,再搬回原本的list中。 - `q_swap`: 這個功能跟 `q_reserveK` , `k` 等於2時的需求相同,所以直接呼叫 `q_reserveK(head, 2)` - `q_sort`: 實作merge sort,先使用與 `q_delete_mid` 相同的方法找到中間的節點,再透過 `list_cut_position` 將鏈結串列切成兩份,然後遞迴的呼叫 `q_sort` 將兩個鏈結串列排序好,再將兩個鏈結串列合併。 - `q_ascend`, `q_descend`: 用 `list_for_each_entry_safe` 走訪所有節點,每個節點都會嘗試刪除之前的節點,直到之前的節點比他大/小。由於大部分的功能幾乎相同,所以分別實作了 `greater` 以及 `less` 傳入 `q_remove_unorder_part` ,來當作判斷是否刪除的依據。 - `q_merge`: 將所有的鏈結串列都合併到第一個鏈結串列中,最後再用 `q_size` 回傳第一個鏈結串列的長度 ### `make test` 在本地端執行`make test`之後發現第 14~16 個測試項目失敗。 `make test` result ``` $ scripts/driver.py -c --- Trace Points +++ TESTING trace trace-01-ops: # Test of insert_head and remove_head --- trace-01-ops 5/5 +++ TESTING trace trace-02-ops: # Test of insert_head, insert_tail, remove_head, remove_tail, and delete_mid --- trace-02-ops 6/6 +++ TESTING trace trace-03-ops: # Test of insert_head, insert_tail, remove_head, reverse and merge --- trace-03-ops 6/6 +++ TESTING trace trace-04-ops: # Test of insert_head, insert_tail, size, swap, and sort --- trace-04-ops 6/6 +++ TESTING trace trace-05-ops: # Test of insert_head, insert_tail, remove_head, reverse, size, swap, and sort --- trace-05-ops 6/6 +++ TESTING trace trace-06-ops: # Test of insert_head, insert_tail, delete duplicate, sort, descend and reverseK --- trace-06-ops 6/6 +++ TESTING trace trace-07-string: # Test of truncated strings --- trace-07-string 6/6 +++ TESTING trace trace-08-robust: # Test operations on empty queue --- trace-08-robust 6/6 +++ TESTING trace trace-09-robust: # Test remove_head with NULL argument --- trace-09-robust 6/6 +++ TESTING trace trace-10-robust: # Test operations on NULL queue --- trace-10-robust 6/6 +++ TESTING trace trace-11-malloc: # Test of malloc failure on insert_head --- trace-11-malloc 6/6 +++ TESTING trace trace-12-malloc: # Test of malloc failure on insert_tail --- trace-12-malloc 6/6 +++ TESTING trace trace-13-malloc: # Test of malloc failure on new --- trace-13-malloc 6/6 +++ TESTING trace trace-14-perf: # Test performance of insert_tail, reverse, and sort ERROR: Time limit exceeded. Either you are in an infinite loop, or your code is too inefficient ERROR: Insertion of dolphin failed (1 failures total) qtest: malloc.c:2617: sysmalloc: Assertion `(old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)' failed. --- trace-14-perf 0/6 +++ TESTING trace trace-15-perf: # Test performance of sort with random and descending orders # 10000: all correct sorting algorithms are expected pass # 50000: sorting algorithms with O(n^2) time complexity are expected failed # 100000: sorting algorithms with O(nlogn) time complexity are expected pass ERROR: Time limit exceeded. Either you are in an infinite loop, or your code is too inefficient Segmentation fault occurred. You dereferenced a NULL or invalid pointer--- trace-15-perf 0/6 +++ TESTING trace trace-16-perf: # Test performance of insert_tail ERROR: Time limit exceeded. Either you are in an infinite loop, or your code is too inefficient ERROR: Insertion of dolphin failed (1 failures total) qtest: malloc.c:2617: sysmalloc: Assertion `(old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)' failed. --- trace-16-perf 0/6 +++ TESTING trace trace-17-complexity: # Test if time complexity of q_insert_tail, q_insert_head, q_remove_tail, and q_remove_head is constant Probably constant time Probably constant time Probably constant time Probably constant time --- trace-17-complexity 5/5 --- TOTAL 82/100 make: *** [Makefile:61: test] Error 1 ``` 從第14個測試項目的輸出可以發現在 `q_insert_head` 的時候會失敗,失敗的原因是 `sysmalloc` 中的 Assertion。 因此,接著執行 `make valgrind` 來找出可能存在的問題,發現有許多的 `Invalid read` 發生在 `q_remove` 的時候,但是第14~16個測試案例都能夠通過。 ``` $ make valgrind # Explicitly disable sanitizer(s) make clean SANITIZER=0 qtest make[1]: Entering directory '/home/matt_jan/lab0-c' rm -f qtest.o report.o console.o harness.o queue.o random.o dudect/constant.o dudect/fixture.o dudect/ttest.o shannon_entropy.o linenoise.o web.o .qtest.o.d .report.o.d .console.o.d .harness.o.d .queue.o.d .random.o.d .dudect/constant.o.d .dudect/fixture.o.d .dudect/ttest.o.d .shannon_entropy.o.d .linenoise.o.d .web.o.d *~ qtest /tmp/qtest.* rm -rf .dudect rm -rf *.dSYM (cd traces; rm -f *~) CC qtest.o CC report.o CC console.o CC harness.o CC queue.o CC random.o CC dudect/constant.o CC dudect/fixture.o CC dudect/ttest.o CC shannon_entropy.o CC linenoise.o CC web.o LD qtest make[1]: Leaving directory '/home/matt_jan/lab0-c' cp qtest /tmp/qtest.XDdHJ4 chmod u+x /tmp/qtest.XDdHJ4 sed -i "s/alarm/isnan/g" /tmp/qtest.XDdHJ4 scripts/driver.py -p /tmp/qtest.XDdHJ4 --valgrind --- Trace Points +++ TESTING trace trace-01-ops: # Test of insert_head and remove_head ==141925== Invalid read of size 8 ==141925== at 0x4852AF8: memmove (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so) ==141925== by 0x10F908: memcpy (string_fortified.h:29) ==141925== by 0x10F908: q_remove (queue.c:101) ==141925== by 0x10F932: q_remove_head (queue.c:112) ==141925== by 0x10C714: queue_remove (qtest.c:353) ==141925== by 0x10C8BC: do_rh (qtest.c:410) ==141925== by 0x10DFD1: interpret_cmda (console.c:181) ==141925== by 0x10E586: interpret_cmd (console.c:201) ==141925== by 0x10E987: cmd_select (console.c:610) ==141925== by 0x10F273: run_console (console.c:705) ==141925== by 0x10D3C3: main (qtest.c:1258) ==141925== Address 0x4b7a448 is 8 bytes before a block of size 2,049 alloc'd ==141925== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so) ==141925== by 0x10C4B3: queue_remove (qtest.c:318) ==141925== by 0x10C8BC: do_rh (qtest.c:410) ==141925== by 0x10DFD1: interpret_cmda (console.c:181) ==141925== by 0x10E586: interpret_cmd (console.c:201) ==141925== by 0x10E987: cmd_select (console.c:610) ==141925== by 0x10F273: run_console (console.c:705) ==141925== by 0x10D3C3: main (qtest.c:1258) --- trace-01-ops 0/5 +++ TESTING trace trace-02-ops: # Test of insert_head, insert_tail, remove_head, remove_tail, and delete_mid --- trace-02-ops 0/6 +++ TESTING trace trace-03-ops: # Test of insert_head, insert_tail, remove_head, reverse and merge --- trace-03-ops 0/6 +++ TESTING trace trace-04-ops: # Test of insert_head, insert_tail, size, swap, and sort --- trace-04-ops 0/6 +++ TESTING trace trace-05-ops: # Test of insert_head, insert_tail, remove_head, reverse, size, swap, and sort --- trace-05-ops 0/6 +++ TESTING trace trace-06-ops: # Test of insert_head, insert_tail, delete duplicate, sort, descend and reverseK --- trace-06-ops 0/6 +++ TESTING trace trace-07-string: # Test of truncated strings --- trace-07-string 0/6 +++ TESTING trace trace-08-robust: # Test operations on empty queue --- trace-08-robust 6/6 +++ TESTING trace trace-09-robust: # Test remove_head with NULL argument --- trace-09-robust 0/6 +++ TESTING trace trace-10-robust: # Test operations on NULL queue --- trace-10-robust 6/6 +++ TESTING trace trace-11-malloc: # Test of malloc failure on insert_head --- trace-11-malloc 6/6 +++ TESTING trace trace-12-malloc: # Test of malloc failure on insert_tail --- trace-12-malloc 6/6 +++ TESTING trace trace-13-malloc: # Test of malloc failure on new --- trace-13-malloc 6/6 +++ TESTING trace trace-14-perf: # Test performance of insert_tail, reverse, and sort --- trace-14-perf 6/6 +++ TESTING trace trace-15-perf: # Test performance of sort with random and descending orders # 10000: all correct sorting algorithms are expected pass # 50000: sorting algorithms with O(n^2) time complexity are expected failed # 100000: sorting algorithms with O(nlogn) time complexity are expected pass --- trace-15-perf 6/6 +++ TESTING trace trace-16-perf: # Test performance of insert_tail --- trace-16-perf 6/6 +++ TESTING trace trace-17-complexity: # Test if time complexity of q_insert_tail, q_insert_head, q_remove_tail, and q_remove_head is constant Probably constant time Probably constant time Probably constant time Probably constant time --- trace-17-complexity 5/5 --- TOTAL 53/100 make: *** [Makefile:73: valgrind] Error 1 ``` :::danger 適度刪減以上輸出,保留關鍵的訊息來討論。 ::: 將`memcpy`改為`strncpy`之後,`make valgrind`的分數得到了滿分,但是在`make test`中仍然無法獲得14~16測試案例的分數。有趣的是當將改動推送到Github時,Git Action的結果表示只有第17項測試案例失敗。 ``` $ make valgrind # Explicitly disable sanitizer(s) make clean SANITIZER=0 qtest make[1]: Entering directory '/home/matt_jan/lab0-c' rm -f qtest.o report.o console.o harness.o queue.o random.o dudect/constant.o dudect/fixture.o dudect/ttest.o shannon_entropy.o linenoise.o web.o .qtest.o.d .report.o.d .console.o.d .harness.o.d .queue.o.d .random.o.d .dudect/constant.o.d .dudect/fixture.o.d .dudect/ttest.o.d .shannon_entropy.o.d .linenoise.o.d .web.o.d *~ qtest /tmp/qtest.* rm -rf .dudect rm -rf *.dSYM (cd traces; rm -f *~) CC qtest.o CC report.o CC console.o CC harness.o CC queue.o CC random.o CC dudect/constant.o CC dudect/fixture.o CC dudect/ttest.o CC shannon_entropy.o CC linenoise.o CC web.o LD qtest make[1]: Leaving directory '/home/matt_jan/lab0-c' cp qtest /tmp/qtest.sPHpZr chmod u+x /tmp/qtest.sPHpZr sed -i "s/alarm/isnan/g" /tmp/qtest.sPHpZr scripts/driver.py -p /tmp/qtest.sPHpZr --valgrind --- Trace Points +++ TESTING trace trace-01-ops: # Test of insert_head and remove_head --- trace-01-ops 5/5 +++ TESTING trace trace-02-ops: # Test of insert_head, insert_tail, remove_head, remove_tail, and delete_mid --- trace-02-ops 6/6 +++ TESTING trace trace-03-ops: # Test of insert_head, insert_tail, remove_head, reverse and merge --- trace-03-ops 6/6 +++ TESTING trace trace-04-ops: # Test of insert_head, insert_tail, size, swap, and sort --- trace-04-ops 6/6 +++ TESTING trace trace-05-ops: # Test of insert_head, insert_tail, remove_head, reverse, size, swap, and sort --- trace-05-ops 6/6 +++ TESTING trace trace-06-ops: # Test of insert_head, insert_tail, delete duplicate, sort, descend and reverseK --- trace-06-ops 6/6 +++ TESTING trace trace-07-string: # Test of truncated strings --- trace-07-string 6/6 +++ TESTING trace trace-08-robust: # Test operations on empty queue --- trace-08-robust 6/6 +++ TESTING trace trace-09-robust: # Test remove_head with NULL argument --- trace-09-robust 6/6 +++ TESTING trace trace-10-robust: # Test operations on NULL queue --- trace-10-robust 6/6 +++ TESTING trace trace-11-malloc: # Test of malloc failure on insert_head --- trace-11-malloc 6/6 +++ TESTING trace trace-12-malloc: # Test of malloc failure on insert_tail --- trace-12-malloc 6/6 +++ TESTING trace trace-13-malloc: # Test of malloc failure on new --- trace-13-malloc 6/6 +++ TESTING trace trace-14-perf: # Test performance of insert_tail, reverse, and sort --- trace-14-perf 6/6 +++ TESTING trace trace-15-perf: # Test performance of sort with random and descending orders # 10000: all correct sorting algorithms are expected pass # 50000: sorting algorithms with O(n^2) time complexity are expected failed # 100000: sorting algorithms with O(nlogn) time complexity are expected pass --- trace-15-perf 6/6 +++ TESTING trace trace-16-perf: # Test performance of insert_tail --- trace-16-perf 6/6 +++ TESTING trace trace-17-complexity: # Test if time complexity of q_insert_tail, q_insert_head, q_remove_tail, and q_remove_head is constant Probably constant time Probably constant time Probably constant time Probably constant time --- trace-17-complexity 5/5 --- TOTAL 100/100 Test with specific case by running command: scripts/driver.py -p /tmp/qtest.sPHpZr --valgrind -t <tid> ``` :::warning 研讀指定的論文 :::