# 2020q1 Homework1 (lab0) contributed by < `hfyeh` > ## 開發紀錄 ### q_size 須檢查 queue 為 `NULL` 的情形。 ```cpp int q_size(queue_t *q) { if (!q) return 0; return q->size; } ``` ### q_new 建立空的 queue 時,必須把 head, tail 和 size 給定正確的初始值。 ```cpp queue_t *q_new() { queue_t *q = malloc(sizeof(queue_t)); if (!q) return NULL; q->head = NULL; q->tail = NULL; q->size = 0; return q; } ``` ### q_insert_head 如果 queue 是空的,表示 `q->head`, `q->tail`, `q->size` 都是初始值,此情況下若此函數被呼叫,`q->head` 和 `q->tail` 都應該指向新插入的 element。反之,`q->tail` 不須改變。 如果 `newh` 有被成功配置,但 `newh->value` 沒有,須將配置給 `newh` 的記憶體空間清空,避免 memory leak。 根據 C99 規格書 [7.24.6.3](http://port70.net/~nsz/c/c11/n1570.html#7.24.6.3),`strlen()` 的回傳值為 terminating null character (即 `\0`)前的字元總數。為了將 `\0` 也複製到 `newh->value` 裡,配置給 `newh->value` 的大小和複製字串的長度都需要 +1。 ```cpp bool q_insert_head(queue_t *q, char *s) { if (!q) return false; list_ele_t *newh = (list_ele_t *) malloc(sizeof(list_ele_t)); if (!newh) { return false; } int len = strlen(s); newh->value = (char *) malloc((len + 1) * sizeof(char)); if (!newh->value) { free(newh); return false; } strncpy(newh->value, s, len + 1); newh->next = q->head; if (!q->head) q->tail = newh; q->head = newh; q->size += 1; return true; } ``` ### q_remove_head `sp` 指向的大小有限制,如果 `q->value` 指向的大小超過該限制,須截斷並補上 `\0`。`sp` 的總長度是 `bufsize`,且題目要求字串必定要以 `\0` 結束,故求得 `q->value` 包含 `\0` 的字串長度 `len+1` 後,以 `maxlen` 存放 `bufsize` 和 `len+1` 的最小值。並且在複製字串後,強制補上 `\0`。 而若 `sp` 為 `NULL`,可直接忽略字串複製。 ```cpp bool q_remove_head(queue_t *q, char *sp, size_t bufsize) { if (!q || !q->head) return false; list_ele_t *del = q->head; q->head = q->head->next; q->size -= 1; if (!q->head) q->tail = NULL; if (sp) { size_t len = strlen(del->value); size_t maxlen = len + 1 > bufsize ? bufsize : len + 1; strncpy(sp, del->value, maxlen - 1); sp[maxlen - 1] = '\0'; } free(del->value); free(del); return true; } ``` ### q_free ```cpp void q_free(queue_t *q) { if (!q) return; list_ele_t *del = q->head; while (del) { q->head = del->next; q->size -= 1; if (!q->head) { q->tail = NULL; } free(del->value); free(del); del = q->head; } free(q); } ``` 後參考 zxcvbnm04987 同學的[作業](https://hackmd.io/@mAleqROqS063_6sD0NUEYg/2020q1-lab0),改為重用 `q_remove_head()` 的版本。 ``` void q_free(queue_t *q) { if (!q) return; while(q_remove_head(q, NULL, 0)) ; free(q); } ``` ### q_insert_tail 與 `q_insert_head` 很相似,同樣需要處理 `queue` 是空的的情形。 ```cpp bool q_insert_tail(queue_t *q, char *s) { if (!q) return false; list_ele_t *newh = (list_ele_t *) malloc(sizeof(list_ele_t)); if (!newh) { return false; } int len = strlen(s); newh->value = (char *) malloc((len + 1) * sizeof(char)); if (!newh->value) { free(newh); return false; } strncpy(newh->value, s, len + 1); newh->next = NULL; if (!q->head) { q->head = newh; } else { q->tail->next = newh; } q->tail = newh; q->size += 1; return true; } ``` ### q_reverse ```cpp void q_reverse(queue_t *q) { if (!q || !q->head) return; list_ele_t *current = q->tail; list_ele_t *prev; while (current != q->head) { prev = q->head; while (current != prev->next) { prev = prev->next; } current->next = prev; current = prev; } current->next = NULL; q->head = q->tail; q->tail = current; } ``` 原本這版從尾端做,太慢了,改為從頭做。 ```cpp void q_reverse(queue_t *q) { if (!q || !q->head || !q->head->next) return; list_ele_t *node = q->head->next; list_ele_t *prev = q->head; list_ele_t *next; while (node->next) { next = node->next; node->next = prev; prev = node; node = next; } q->tail = q->head; q->tail->next = NULL; q->head = node; q->head->next = prev; } ``` ### q_sort ~~暫時先用 bubble sort。~~ ```cpp void q_sort(queue_t *q) { if (!q || !q->head || q->size == 1) return; list_ele_t *current = q->head; list_ele_t *compare; char *tmp; while (current->next) { compare = current->next; while (compare) { if (current->value[0] > compare->value[0]) { tmp = current->value; current->value = compare->value; compare->value = tmp; } compare = compare->next; } current = current->next; } } ``` 改為 selection sort。原本的 bubble sort 交換次數太多,參考 [Linked list sort](https://npes87184.github.io/2015-09-12-linkedListSort/),改用 selection sort 排序,在每個 sub-iteration 只把最小值存下來,iteration 結束前才做資料交換。 ```cpp void q_sort(queue_t *q) { if (!q || !q->head || q->size == 1) return; list_ele_t *current = q->head; list_ele_t *select; list_ele_t *min; char *tmp; while (current->next) { select = current->next; min = current; while (select) { if (strnatcasecmp(min->value, select->value) == 1) { min = select; } select = select->next; } tmp = min->value; min->value = current->value; current->value = tmp; current = current->next; } } ``` 參考自己寫的[第 1 週測驗題延伸問題](https://hackmd.io/@sharefun/H1SKqXrNI),把 sorting 的演算法改為改良後的 merge sort。 :::warning TODO: 換為 merge sort ::: 這邊是直接拿第一個字元比較,但其實 [C99 規格書](http://port70.net/~nsz/c/c11/n1570.html#7.24.4.2) 就有提到可以用 `strcmp()` 比較字串大小了。 作業提示須使用 natural sort,原因是 `strcmp()` 會判斷 `"a2" > "a10"`(以第一個相異的字元依 ASCII code 去比對大小)。要使上述判斷大小正確,可以限制字元數必須相等,如 `"a2"` 改為 `"a02"`,或是使用 natural sort。 1. 把 strnatcmp.[ch] 放到本專案下 2. 修改 Makefile ``` # Makefile OBJS := qtest.o report.o console.o harness.o queue.o \ - random.o dudect/constant.o dudect/fixture.o dudect/ttest.o + random.o dudect/constant.o dudect/fixture.o dudect/ttest.o \ + strnatcmp.o ``` 3. queue.c 加入`#include "strnatcmp.h"` 並修改 `q_sort()`,使用 `strnatcmp()` 4. qtest.c 加入`#include "strnatcmp.h"` 並移除 `#include <string.h>`,在 `do_sort()` 把 `strcasecmp()` 改為 `strnatcmp()` :::warning TODO: FIXME 尚未完成 ::: ### Valgrind and Massif 初次實做完各主要功能後,開始嘗試使用 Valgrind 分析記憶體。 :::info 執行 `make valgrind` 時呼叫 subprocess 時會發生 `[Errno 2] No such file or directory` 錯誤,後已排除問題。 原問題如下: ``` $ make valgrind # Explicitly disable sanitizer(s) make clean SANITIZER=0 qtest make[1]: Entering directory '/home/sharefun/Sources/sysprog2020/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 .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 *~ 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 LD qtest make[1]: Leaving directory '/home/sharefun/Sources/sysprog2020/lab0-c' cp qtest /tmp/qtest.GaS1n5 chmod u+x /tmp/qtest.GaS1n5 sed -i "s/alarm/isnan/g" /tmp/qtest.GaS1n5 scripts/driver.py -p /tmp/qtest.GaS1n5 --valgrind - Trace Points + TESTING trace trace-01-ops: Call of 'valgrind /tmp/qtest.GaS1n5 -v 1 -f ./traces/trace-01-ops.cmd' failed: [Errno 2] No such file or directory - trace-01-ops 0/6 + TESTING trace trace-02-ops: Call of 'valgrind /tmp/qtest.GaS1n5 -v 1 -f ./traces/trace-02-ops.cmd' failed: [Errno 2] No such file or directory --- trace-02-ops 0/6 ... ``` 檢查 `/tmp` 下有該檔案,且直接執行 `valgrind /tmp/qtest.GaS1n5 -v 1 -f ./traces/trace-02-ops.cmd` 也可以成功。但是執行 `scripts/driver.py -p /tmp/qtest.vrlE1o --valgrind -t 02` 則否,與上面有相同的錯誤訊息。 我的環境是 Linux sharefun-X230 5.3.0-40-generic #32~18.04.1-Ubuntu SMP Mon Feb 3 14:05:59 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux,`/usr/bin/python` 版本是 2.7.17,shell 使用 zsh,改用 bash 也有一樣的問題。 最後是把 script/driver.py 呼叫外部程式的 `subprocess.call()` arguments 改成 ``` // script/driver.py - retcode = subprocess.call(clist) + retcode = subprocess.call(" ".join(clist), shell=True) ``` 就可以正常執行了。 :::