# 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
研讀指定的論文
:::