contributed by < Korin777
>
$ 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
malloc
一個 head
並回傳 , 後來發現這樣就無法透過 list.h
所提供的 list_empty
來判斷 list 是否為空 , 且這樣的 linked list 也並不是雙向環狀的list.h
提供的 INIT_LIST_HEAD
將 head
的 prev
及 next
都指向自己 , 完成雙向環狀的 linked liststruct list_head *q_new()
{
struct list_head *head = malloc(sizeof(struct list_head));
if (!head)
return NULL;
INIT_LIST_HEAD(head);
return head;
}
list.h
提供的 list_for_each_entry_safe
遍歷 list 並釋放每個 entry
所配置過的記憶體空間entry
皆為 element_t
entry
要先釋放 element_t ->value
在釋放 element_t
head
, 因為它並不是 list
中的一個 entry
而是用來當作 list 的開頭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);
}
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 leaklen
變數來儲存 s
的長度 , 減少重複呼叫 strlen
所花的時間 , 不過在測試 time complexity 有時還是會沒通過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_head
差再需用 list.h
提供的list_add_tail
來連接節點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;
}
list.h
所提供的 list_entry
, 有了包含在 element_t
中的 list
記憶體位置 , 就能算出此 element_t
的記憶體位置element_t->value
複製到 sp
並將最後一個字元設為空字元 \0
list.h
所提供的 list_del
, 將節點移除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_head
差在移除的節點為 head->prev
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;
}
list.h
提供的 list_for_each
遍歷 linked list 來取長度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;
}
h
一開始為第一個節點 , t
一開始為最後一個節點h
一直往後走 , t
則一直往前走 , 當兩者相同或 h->next
與 t
相同時 , h
恰好為中點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;
}
tmp
為字元指標 , 配置記憶體前 tmp
不為 NULL 要先釋放 tmp
的記憶體 , 以免產生 memory leaktmp
為 NULL 或 tmp
和當前 entry
的字串不同 , 就看下個 entry
的字串是否與當前字串一樣 , 來判斷是否該移除當前節點及釋放對應的記憶體tmp
和當前 entry
的字串相同 , 直接移除當前節點及釋放對應的記憶體tmp
最後不為 NULL 要釋放 tmp
記憶體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;
}
li
為相鄰 node 的第一個 node , tmp
則為第二個 node , 並將兩者互換li
為 head
或 tmp
為 head
, 表示已經沒有相鄰的 node 需要互換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;
}
}
h
一開始為第一個節點 , t
一開始為最後一個節點h
一直往後走 , t
則一直往前走 , 因為 h
跟 t
互換 , h
要更新為 t->next
, t
則更新為 h->next
h
與 t
相同或 t->next->prev
與 t
相同 , linked list 已反轉完畢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
應該都是錯的trace-17-complexity
還是沒過 , 在本地端測試確實有時不會過 , 還要在看看是那出了問題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;
}
}
qtest
跑 trace-14-perf.cmd
的測資 , 發現會 Segmetation Faultstruct 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);
}
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;
}
}
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);
}
next
改為 null , 在 split
時才不會又跑回最前面head
的 prev
、 最後一個節點的 next
及第一個節點的 prev
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;
}
mergeTwoLists
, 透過指標的指標實做 merge
函式 , 簡化程式碼prev
指標並不會被修改 , 必須在 mergeSort
後把它改回來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;
}
queue.c
中entry
的字串大小的 compare 函式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);
}
沿用 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 |
console_init
新增 shuffle 指令ADD_COMMAND
是定義在 console.h
的巨集 #define ADD_COMMAND(cmd, msg) add_cmd(#cmd, do_##cmd, msg)
, 也是為什麼當我們想新增一個指令 cmd
, 對應的 function 一定要是 do_cmd
ADD_COMMAND(shuffle, " | Do Fisher–Yates shuffle in queue");
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();
}
shuffle_point
為當前要調換位置的節點 , next_shuffle_point
為下一個要調換的節點prev
之後next_shuffle_point
在互換節點相鄰時 , 節點互換後這時shuffle_point
即為下次的 shuffle_point
, 所以 next_shuffle_point
要改為 shuffle_point->prev
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;
}
}
}
透過 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
linenoiseHistoryLoad(HISTORY_FILE); /* Load the history at startup */
linenoise.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 的問題void freelinenoise() {
linenoiseAtExit();
}