# 2022q1 Homework1 (lab0)
contributed by < `Korin777` >
## 開發環境
```shell
$ gcc --version
gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
Address sizes: 39 bits physical, 48 bits virtual
CPU(s): 8
On-line CPU(s) list: 0-7
Thread(s) per core: 2
Core(s) per socket: 4
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 142
Model name: Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz
Stepping: 10
CPU MHz: 1800.000
CPU max MHz: 3400.0000
CPU min MHz: 400.0000
BogoMIPS: 3600.00
Virtualization: VT-x
L1d cache: 128 KiB
L1i cache: 128 KiB
L2 cache: 1 MiB
L3 cache: 6 MiB
NUMA node0 CPU(s): 0-7
```
## queue.c 實作
### q_new
* 一開始我是直接 `malloc` 一個 `head` 並回傳 , 後來發現這樣就無法透過 `list.h` 所提供的 `list_empty` 來判斷 list 是否為空 , 且這樣的 linked list 也並不是雙向環狀的
* 後來透過 `list.h` 提供的 `INIT_LIST_HEAD` 將 `head` 的 `prev` 及 `next` 都指向自己 , 完成雙向環狀的 linked list
```c
struct list_head *q_new()
{
struct list_head *head = malloc(sizeof(struct list_head));
if (!head)
return NULL;
INIT_LIST_HEAD(head);
return head;
}
```
### q_free
* 透過 `list.h` 提供的 `list_for_each_entry_safe` 遍歷 list 並釋放每個 `entry` 所配置過的記憶體空間
* list 的每個 `entry` 皆為 `element_t`
* 一個 `entry` 要先釋放 `element_t ->value` 在釋放 `element_t`
* 最後還要再釋放 list 的 `head` , 因為它並不是 `list` 中的一個 `entry` 而是用來當作 list 的開頭
```c
void q_free(struct list_head *l)
{
if (!l)
return;
element_t *ele, *tmpele;
list_for_each_entry_safe (ele, tmpele, l, list)
free(ele->value), free(ele);
free(l);
}
```
### q_insert_head
* `malloc` 一個 `element_t` 作為 list 中一個 `entry`
* `element_t->value` 的 size 為 `strlen(s)+1` , 最後一個字元用來存放空字元 `\0`
* `element_t->list` 是 `struct list_head` 的型態 , 為實際 linked list 互相連接的節點 , 透過 `list.h` 提供的 `list_add` 來將它加在 linked list 的最前面(不包含`head`)
* 當配置 `element_t ->value` 記憶體失敗時 , 要釋放`element_t` 的記憶體 , 才不會產生 memory leak
* 特別用一個 `len` 變數來儲存 `s` 的長度 , 減少重複呼叫 `strlen` 所花的時間 , 不過在測試 time complexity 有時還是會沒通過
* 後來發現 time complexity 沒過得原因在 q_reverse 寫錯了
```c
bool q_insert_head(struct list_head *head, char *s)
{
if (!head)
return false;
element_t *ele = malloc(sizeof(element_t));
if (!ele)
return false;
int len = strlen(s);
ele->value = malloc(len + 1);
if (!ele->value) {
free(ele);
return false;
}
strncpy(ele->value, s, len);
ele->value[len] = '\0';
list_add(&ele->list, head);
return true;
}
```
### q_insert_tail
* 和 `q_insert_head` 差再需用 `list.h` 提供的`list_add_tail` 來連接節點
```c
bool q_insert_tail(struct list_head *head, char *s)
{
if (!head)
return false;
element_t *ele = malloc(sizeof(element_t));
if (!ele)
return false;
int len = strlen(s);
ele->value = malloc(len + 1);
if (!ele->value) {
free(ele);
return false;
}
strncpy(ele->value, s, len);
ele->value[len] = '\0';
list_add_tail(&ele->list, head);
return true;
}
```
### q_remove_head
* 透過 `list.h` 所提供的 `list_entry` , 有了包含在 `element_t` 中的 `list` 記憶體位置 , 就能算出此 `element_t` 的記憶體位置
* 將 `element_t->value` 複製到 `sp` 並將最後一個字元設為空字元 `\0`
* 透過 `list.h` 所提供的 `list_del` , 將節點移除
```c
element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize)
{
if (!head || list_empty(head))
return NULL;
struct list_head *node = head->next;
element_t *ele = list_entry(node, element_t, list);
if (sp) {
strncpy(sp, ele->value, bufsize - 1);
sp[bufsize - 1] = '\0';
}
list_del(node);
return ele;
}
```
### q_remove_tail
* 和 `q_remove_head` 差在移除的節點為 `head->prev`
```c
element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize)
{
if (!head || list_empty(head))
return NULL;
struct list_head *node = head->prev;
element_t *ele = list_entry(node, element_t, list);
if (sp) {
strncpy(sp, ele->value, bufsize - 1);
sp[bufsize - 1] = '\0';
}
list_del(node);
return ele;
}
```
### q_size
* 透過 `list.h` 提供的 `list_for_each` 遍歷 linked list 來取長度
```c
int q_size(struct list_head *head)
{
if (!head)
return 0;
int len = 0;
struct list_head *li;
list_for_each (li, head)
len++;
return len;
}
```
### q_delete_mid
* `h` 一開始為第一個節點 , `t` 一開始為最後一個節點
* `h` 一直往後走 , `t` 則一直往前走 , 當兩者相同或 `h->next` 與 `t` 相同時 , `h` 恰好為中點
```c
bool q_delete_mid(struct list_head *head)
{
// https://leetcode.com/problems/delete-the-middle-node-of-a-linked-list/
if (!head || list_empty(head))
return false;
struct list_head *h = head->next, *t = head->prev;
while (h != t) {
if (h->next == t)
break;
h = h->next, t = t->prev;
}
element_t *ele = list_entry(h, element_t, list);
list_del(h);
free(ele->value), free(ele);
return true;
}
```
### q_delete_dup
* `tmp` 為字元指標 , 配置記憶體前 `tmp` 不為 NULL 要先釋放 `tmp` 的記憶體 , 以免產生 memory leak
* `tmp`為 NULL 或 `tmp` 和當前 `entry` 的字串不同 , 就看下個 `entry` 的字串是否與當前字串一樣 , 來判斷是否該移除當前節點及釋放對應的記憶體
* `tmp` 和當前 `entry` 的字串相同 , 直接移除當前節點及釋放對應的記憶體
* 若 `tmp` 最後不為 NULL 要釋放 `tmp` 記憶體
```c
bool q_delete_dup(struct list_head *head)
{
// https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/
if (!head)
return false;
char *tmp = NULL;
element_t *ele, *tmpele;
list_for_each_entry_safe (ele, tmpele, head, list) {
if (!tmp || strcmp(tmp, ele->value) != 0) {
if (ele->list.next && ele->list.next != head) {
element_t *elen = list_entry(ele->list.next, element_t, list);
if (strcmp(elen->value, ele->value) == 0) {
if (tmp)
free(tmp);
tmp = malloc(sizeof(ele->value));
strncpy(tmp, ele->value, strlen(ele->value));
list_del(&ele->list);
free(ele->value), free(ele);
}
}
} else {
list_del(&ele->list);
free(ele->value), free(ele);
}
}
if (tmp)
free(tmp);
return true;
}
```
### q_swap
* `li` 為相鄰 node 的第一個 node , `tmp` 則為第二個 node , 並將兩者互換
* 當 `li` 為 `head` 或 `tmp` 為 `head` , 表示已經沒有相鄰的 node 需要互換
```c
void q_swap(struct list_head *head)
{
// https://leetcode.com/problems/swap-nodes-in-pairs/
if (!head || list_empty(head) || list_is_singular(head))
return;
struct list_head *li = head->next, *tmp;
while (li != head) {
tmp = li->next;
if (tmp == head)
break;
li->prev->next = tmp;
tmp->next->prev = li;
li->next = tmp->next;
tmp->next = li;
tmp->prev = li->prev;
li->prev = tmp;
li = li->next;
}
}
```
### q_reverse
* `h` 一開始為第一個節點 , `t` 一開始為最後一個節點
* `h` 一直往後走 , `t` 則一直往前走 , 因為 `h` 跟 `t` 互換 , `h` 要更新為 `t->next` , `t` 則更新為 `h->next`
* 當 `h` 與 `t` 相同或 `t->next->prev` 與 `t` 相同 , linked list 已反轉完畢
```c
void q_reverse(struct list_head *head)
{
struct list_head *tmp, *htmp, *ttmp;
if (!head || list_is_singular(head))
return;
struct list_head *h = head->next, *t = head->prev;
head->next = t;
head->prev = h;
while (h != t) {
htmp = h->next;
ttmp = t->prev;
tmp = h->next;
h->next = h->prev;
h->prev = tmp;
tmp = t->next;
t->next = t->prev;
t->prev = tmp;
if (t->next->prev == t)
break;
h = htmp, t = ttmp;
}
if (h == t) {
tmp = h->prev;
h->prev = h->next;
h->next = tmp;
}
}
```
* 原本以為 `trace-17-complexity` 有時會沒 pass 的原因只跟 q_insert_tail, q_insert_head, q_remove_tail, q_remove_head 有關 , 後來發現原來是我 q_reverse 寫錯了 , `t->prev` 是 `t->next` 才對 , 而 `tmp` 的宣告也是多餘的
* 不過上面那個錯的 `reverse` 還是能成功將 reverse 後的 linke list show 出來 , 儘管前半段 linked list 節點的 `prev` 應該都是錯的
* 後來推上 github `trace-17-complexity` 還是沒過 , 在本地端測試確實有時不會過 , 還要在看看是那出了問題
```c
void q_reverse(struct list_head *head)
{
struct list_head *htmp, *ttmp;
if (!head || list_is_singular(head))
return;
struct list_head *h = head->next, *t = head->prev;
head->next = t;
head->prev = h;
while (h != t) {
htmp = h->next;
ttmp = t->prev;
h->next = h->prev;
h->prev = htmp;
t->prev = t->next;
t->next = ttmp;
if (t->next->prev == t)
break;
h = htmp, t = ttmp;
}
if (h == t) {
htmp = h->prev;
h->prev = h->next;
h->next = htmp;
}
}
```
### q_sort
* 參考 [geekforgeek](https://www.geeksforgeeks.org/merge-sort-for-doubly-linked-list/) 實作出適用於 Circular Doubly Linked List 的遞迴法(Top-down) Merge Sort
* 在 Test performance 無法 pass , 在 `qtest` 跑 `trace-14-perf.cmd` 的測資 , 發現會 Segmetation Fault
* 自己測試後發現 , list 的 size 在約 1400000 就會 Segmetation Fault , 應該是 function 遞迴太多次的關係 , 所以下面改用迭代法(Bottom-up)實作
```c
struct list_head *merge(struct list_head *first, struct list_head *second)
{
if (!first)
return second;
if (!second)
return first;
element_t *elef = list_entry(first,element_t,list), *eles = list_entry(second,element_t,list);
// elef->value < eles->value
if(strcmp(elef->value,eles->value) < 0)
{
first->next = merge(first->next,second);
first->next->prev = first;
first->prev = NULL;
return first;
}
else // elef->value >= eles->value
{
second->next = merge(first,second->next);
second->next->prev = second;
second->prev = NULL;
return second;
}
}
struct list_head *split(struct list_head *head)
{
struct list_head *fast = head, *slow = head;
while (fast->next && fast->next->next) {
fast = fast->next->next;
slow = slow->next;
}
struct list_head *temp = slow->next;
slow->next = NULL;
return temp;
}
struct list_head *mergeSort(struct list_head *head)
{
if (!head || !head->next) {
return head;
}
struct list_head *second = split(head);
head = mergeSort(head);
second = mergeSort(second);
return merge(head, second);
}
```
* 改用迭代法(Bottom-up)實作
* 在 Test performance 還是無法 pass , 在 qtest 跑 trace-14-perf.cmd 的測資 , 雖然解決了 Segmentation Fault 的問題 卻產生了 time limit exceeded
* 因為 linked list 記憶體並不連續 , 無法像陣列那樣直接取得某個位置的值 , 而必須慢慢往後走 , 導致當要 merge 兩個子 list 前會產生許多很長的走訪
* 所以我沿用原本的遞迴 merge sort , 但將 merge 的過程改為迭代
```c
void merge_iterative(struct list_head *first, struct list_head *second, int
ls, int rs) {
int l = 0, r = 0;
element_t *elef = list_entry(first,element_t,list), *eles = list_entry(second,element_t,list); while(l < ls && r < rs) {
if(strcmp(elef->value,eles->value) < 0)
{
first = first->next;
l++;
if(l < ls)
elef = list_entry(first,element_t,list);
}
else // elef->value >= eles->value
{
struct list_head *tmp = second->next;
list_del(second);
list_add_tail(second,first);
second = tmp;
r++;
if(r < rs)
eles = list_entry(second,element_t,list);
}
}
}
void mergeSort_iterative(struct list_head *head,int size)
{
int sz = 1,n = size;
while(sz < n) {
struct list_head *f = head->next,*s = head->next, *nf = f, *ns;
int i = 0;
int j;
for(j = 0; j < sz; j++)
s = s->next;
ns = s;
while(i < n-1) {
if(size - i <= sz)
break;
int l = sz, r = sz;
if(n-i-sz < sz)
r = n-i-sz;
for(j = 0; j < sz*2; j++) {
if(nf->next)
nf = nf->next;
if(ns->next)
ns = ns->next;
}
merge_iterative(f,s,l,r);
f = nf;
s = ns;
i += sz*2;
}
sz += sz;
}
}
```
* 將 merge 改為迭代的遞迴 mergesort , 總算通過 Test performance
* merge 用來將兩個子 list 合併 , 回傳合併後的head
* split 將 list 切半為兩個子 list
```c
struct list_head *merge(struct list_head *first, struct list_head *second)
{
if (!first)
return second;
if (!second)
return first;
struct list_head *tmphead = NULL, *tf = first, *ts = second;
element_t *elef = list_entry(first, element_t, list),
*eles = list_entry(second, element_t, list);
while (first && second) {
if (strcmp(elef->value, eles->value) < 0) {
if (!tmphead)
tmphead = first;
tf = first;
first = first->next;
if (first)
elef = list_entry(first, element_t, list);
} else // elef->value >= eles->value
{
if (!tmphead)
tmphead = second;
struct list_head *tmp = second->next;
struct list_head *next = second->next;
struct list_head *prev = second->prev;
if (next)
next->prev = prev;
if (prev->next == second)
prev->next = next;
prev = first->prev;
if (prev->next == first)
prev->next = second;
if (second->next)
second->next = first;
second->prev = prev;
first->prev = second;
ts = second;
second = tmp;
if (second)
eles = list_entry(second, element_t, list);
}
}
if (first) {
ts->next = first;
first->prev = ts;
}
if (second) {
tf->next = second;
second->prev = tf;
}
return tmphead;
}
struct list_head *split(struct list_head *head)
{
struct list_head *fast = head, *slow = head;
while (fast->next && fast->next->next) {
fast = fast->next->next;
slow = slow->next;
}
struct list_head *temp = slow->next;
slow->next = NULL;
return temp;
}
struct list_head *mergeSort(struct list_head *head)
{
if (!head || !head->next) {
return head;
}
struct list_head *second = split(head);
head = mergeSort(head);
second = mergeSort(second);
return merge(head, second);
}
```
* 將 linked list 最後一個節點的 `next` 改為 null , 在 `split` 時才不會又跑回最前面
* 做完 merge sort 要更改 `head` 的 `prev` 、 最後一個節點的 `next` 及第一個節點的 `prev`
```c
void q_sort(struct list_head *head)
{
if (!head || list_empty(head) || list_is_singular(head))
return;
head->prev->next = NULL;
head->next = mergeSort(head->next);
struct list_head *li = head;
while (li->next) {
li = li->next;
}
head->prev = li;
head->prev->next = head;
head->next->prev = head;
}
```
* 參考 [你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list) 中的 `mergeTwoLists` , 透過指標的指標實做 `merge` 函式 , 簡化程式碼
* 用這方式 merge 完後的 linked list `prev` 指標並不會被修改 , 必須在 `mergeSort` 後把它改回來
```c
struct list_head *merge(struct list_head *first, struct list_head *second)
{
struct list_head *head = NULL, **ptr = &head, **node;
for(node = NULL; first && second; *node = (*node)->next) {
element_t *elef = list_entry(first, element_t, list), *eles = list_entry(second, element_t, list);
node = (strcmp(elef->value, eles->value) <= 0) ? &first : &second;
*ptr = *node;
ptr = &(*ptr)->next;
}
*ptr = (struct list_head *)((uintptr_t) first | (uintptr_t) second);
return head;
}
void q_sort(struct list_head *head)
{
if (!head || list_empty(head) || list_is_singular(head))
return;
head->prev->next = NULL;
head->next = mergeSort(head->next);
struct list_head *fix_prev ,*li = head;
while (li->next) {
fix_prev = li;
li = li->next;
li->prev = fix_prev;
}
head->prev = li;
head->prev->next = head;
}
```
## Linux list_sort
### 引入 [lib/list_sort.c](https://github.com/torvalds/linux/blob/master/lib/list_sort.c) 到 lab0-c
* 將 linux listsort 程式碼拿到 `queue.c` 中
* 宣告用來比較 queue 每個 `entry` 的字串大小的 compare 函式
```c
int list_val_cmp(void *priv, const struct list_head *a, const struct list_head *b)
{
element_t *elea = list_entry(a,element_t,list), *eleb = list_entry(b,element_t,list);
// elea->value <= eleb->value return <= 0
// elea->value > eleb->value return > 0
return strcmp(elea->value,eleb->value);
}
void q_sort(struct list_head *head)
{
if (!head || list_empty(head) || list_is_singular(head))
return;
list_sort(NULL,head,list_val_cmp);
}
```
### 比較自己實做的 Merge Sort 和 Linux 核心程式碼之間效能落差
沿用 `trace-14-perf.cmd` 的測資 , 並透過 `qtest` 所提供的 `time` 命令來測量排序時間
```
option fail 0
option malloc 0
new
ih dolphin 1000000
it gerbil 1000000
reverse
time
sort
time
```
| 排序函式 | 執行時間 |
| --- | --- |
| My MergeSort | 0.95s |
| Linux MergeSort | 0.55s |
## 在 qtest 提供新的命令 shuffle
### 在 qtest.c 加上 shuffle 指令
* 在 `console_init` 新增 shuffle 指令
* `ADD_COMMAND` 是定義在 `console.h` 的巨集 `#define ADD_COMMAND(cmd, msg) add_cmd(#cmd, do_##cmd, msg)` , 也是為什麼當我們想新增一個指令 `cmd` , 對應的 function 一定要是 `do_cmd`
```c
ADD_COMMAND(shuffle, " | Do Fisher–Yates shuffle in queue");
```
```c
static bool do_shuffle(int argc, char *argv[])
{
if (argc != 1) {
report(1, "%s takes no arguments", argv[0]);
return false;
}
if (!l_meta.l)
report(3, "Warning: Try to access null queue");
error_check();
set_noallocate_mode(true);
if (exception_setup(true))
shuffle(l_meta.l);
exception_cancel();
set_noallocate_mode(false);
show_queue(3);
return !error_check();
}
```
### 實做 Fisher–Yates shuffle
* Fisher–Yates shuffle
1. 有一長度為 len 的 list , 設 i 初始為 len
2. 每次隨機取一數 j , 0 <= j <= i
3. 將第 j 個節點與第 i 個節點互換 , 並將 i - 1
4. 重複步驟 2 、 3 直到 i = 1
* `shuffle_point` 為當前要調換位置的節點 , `next_shuffle_point` 為下一個要調換的節點
* 當要互換的節點是同一個節點 , 不做任何事
* 當要互換的節點相鄰 , 移除第一個節點並接在第二個之後
* 其餘的情況 , 先移除第一個節點並接在第二個之後 , 再移除第二個節點並接在原本第一個節點的 `prev` 之後
* `next_shuffle_point` 在互換節點相鄰時 , 節點互換後這時`shuffle_point` 即為下次的 `shuffle_point` , 所以 `next_shuffle_point` 要改為 `shuffle_point->prev`
```c
void shuffle(struct list_head *head)
{
if (!head || list_empty(head) || list_is_singular(head))
return;
int len = q_size(head), x, it;
struct list_head *li, *shuffle_point = head->prev, *tmp, *next_shuffle_point = shuffle_point->prev;
while(len-- > 1) {
li = head->next, it = 0, x = rand() % len;
while (it++ < x) {
li = li->next;
}
if(li == shuffle_point);
else if(li->next == shuffle_point) {
list_del(li);
list_add(li,shuffle_point);
}
else {
tmp = li->prev;
list_del(li);
list_add(li,shuffle_point);
list_del(shuffle_point);
list_add(shuffle_point, tmp);
}
if(next_shuffle_point != shuffle_point) {
shuffle_point = next_shuffle_point;
next_shuffle_point = next_shuffle_point->prev;
next_shuffle_point = shuffle_point->prev;
}
else {
next_shuffle_point = shuffle_point->prev;
}
}
}
```
## 運用 Valgrind 排除 qtest 實作的記憶體錯誤
透過 make valgrind 可以看到以下訊息 , 這裡取 `trace-01-ops` 產生的訊息來看 , 看起來像是有記憶體沒有釋放
```
+++ TESTING trace trace-01-ops:
# Test of insert_head and remove_head
==19148== 5 bytes in 1 blocks are still reachable in loss record 1 of 3
==19148== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==19148== by 0x4A4D50E: strdup (strdup.c:42)
==19148== by 0x110C05: linenoiseHistoryAdd (linenoise.c:1240)
==19148== by 0x111798: linenoiseHistoryLoad (linenoise.c:1329)
==19148== by 0x10C861: main (qtest.c:1016)
==19148==
==19148== 137 bytes in 19 blocks are still reachable in loss record 2 of 3
==19148== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==19148== by 0x4A4D50E: strdup (strdup.c:42)
==19148== by 0x110B79: linenoiseHistoryAdd (linenoise.c:1240)
==19148== by 0x111798: linenoiseHistoryLoad (linenoise.c:1329)
==19148== by 0x10C861: main (qtest.c:1016)
==19148==
==19148== 160 bytes in 1 blocks are still reachable in loss record 3 of 3
==19148== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==19148== by 0x110BC5: linenoiseHistoryAdd (linenoise.c:1228)
==19148== by 0x111798: linenoiseHistoryLoad (linenoise.c:1329)
==19148== by 0x10C861: main (qtest.c:1016)
==19148==
--- trace-01-ops 5/5
```
`qtest.c`
```c
linenoiseHistoryLoad(HISTORY_FILE); /* Load the history at startup */
```
`linenoise.c`
```c
int linenoiseHistoryAdd(const char *line)
{
char *linecopy;
if (history_max_len == 0)
return 0;
/* Initialization on first call. */
if (history == NULL) {
history = malloc(sizeof(char *) * history_max_len);
if (history == NULL)
return 0;
memset(history, 0, (sizeof(char *) * history_max_len));
}
/* Don't add duplicated lines. */
if (history_len && !strcmp(history[history_len - 1], line))
return 0;
/* Add an heap allocated copy of the line in the history.
* If we reached the max length, remove the older line. */
linecopy = strdup(line);
if (!linecopy)
return 0;
if (history_len == history_max_len) {
free(history[0]);
memmove(history, history + 1, sizeof(char *) * (history_max_len - 1));
history_len--;
}
history[history_len] = linecopy;
history_len++;
return 1;
}
int linenoiseHistoryLoad(const char *filename)
{
FILE *fp = fopen(filename, "r");
char buf[LINENOISE_MAX_LINE];
if (fp == NULL)
return -1;
while (fgets(buf, LINENOISE_MAX_LINE, fp) != NULL) {
char *p;
p = strchr(buf, '\r');
if (!p)
p = strchr(buf, '\n');
if (p)
*p = '\0';
linenoiseHistoryAdd(buf);
}
fclose(fp);
return 0;
}
```
* 一開始以為是 `linenoiseHistoryAdd` 的 `linecopy` 沒有釋放 , 因為他透過 `strdup` 配置記憶體卻沒釋放 , 便嘗試在函式結束前將它釋放 , 結果訊息卻便多了
```
+++ TESTING trace trace-01-ops:
==19917== Invalid read of size 1
==19917== at 0x483FED4: strcmp (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==19917== by 0x110B6D: linenoiseHistoryAdd (linenoise.c:1235)
==19917== by 0x1117A0: linenoiseHistoryLoad (linenoise.c:1330)
==19917== by 0x10C861: main (qtest.c:1016)
==19917== Address 0x4ba1ed0 is 0 bytes inside a block of size 5 free'd
==19917== at 0x483CA3F: free (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==19917== by 0x110C29: linenoiseHistoryAdd (linenoise.c:1249)
==19917== by 0x1117A0: linenoiseHistoryLoad (linenoise.c:1330)
==19917== by 0x10C861: main (qtest.c:1016)
==19917== Block was alloc'd at
==19917== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==19917== by 0x4A4D50E: strdup (strdup.c:42)
==19917== by 0x110C05: linenoiseHistoryAdd (linenoise.c:1240)
==19917== by 0x1117A0: linenoiseHistoryLoad (linenoise.c:1330)
==19917== by 0x10C861: main (qtest.c:1016)
==19917==
==19917== Invalid read of size 1
==19917== at 0x483FEE8: strcmp (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==19917== by 0x110B6D: linenoiseHistoryAdd (linenoise.c:1235)
==19917== by 0x1117A0: linenoiseHistoryLoad (linenoise.c:1330)
==19917== by 0x10C861: main (qtest.c:1016)
==19917== Address 0x4ba21f1 is 1 bytes inside a block of size 14 free'd
==19917== at 0x483CA3F: free (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==19917== by 0x110C29: linenoiseHistoryAdd (linenoise.c:1249)
==19917== by 0x1117A0: linenoiseHistoryLoad (linenoise.c:1330)
==19917== by 0x10C861: main (qtest.c:1016)
==19917== Block was alloc'd at
==19917== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==19917== by 0x4A4D50E: strdup (strdup.c:42)
==19917== by 0x110B79: linenoiseHistoryAdd (linenoise.c:1240)
==19917== by 0x1117A0: linenoiseHistoryLoad (linenoise.c:1330)
==19917== by 0x10C861: main (qtest.c:1016)
==19917==
# Test of insert_head and remove_head
==19917== 160 bytes in 1 blocks are still reachable in loss record 1 of 1
==19917== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==19917== by 0x110BC5: linenoiseHistoryAdd (linenoise.c:1228)
==19917== by 0x1117A0: linenoiseHistoryLoad (linenoise.c:1330)
==19917== by 0x10C861: main (qtest.c:1016)
==19917==
--- trace-01-ops 0/5
```
* 決定先看看 `linenoiseHistoryLoad(HISTORY_FILE)` 的 `HISTORY_FILE` 是什麼 , 發現原來是紀錄 `qtest cmd` 曾打過的指令紀錄 `.cmd_history` , 而這些紀錄會記在 `**history` 這個類似字串陣列裡頭
* 所以嘗試把這個 `histroy` 給釋放 , 寫了一個freeHistory 的函式來完成 , 卻發現 `linenoise.c` 原本就有定義這個函式了
* 最後 , 在 `linenoise.c` 裡寫一個函式 `freelinenoise` 來呼叫釋放 histroy 記憶體的函式 `linenoiseAtExit` , 並在 `qtest` 主程式結束前呼叫它 , 成功解決 memory leak 的問題
```c
void freelinenoise() {
linenoiseAtExit();
}
```