--- tags: linux2020 --- # 2020q1 Homework1 (lab0) contributed by < `bananemo` > ## 作業說明 * [作業要求](https://hackmd.io/@sysprog/linux2020-lab0) * [作業區](https://hackmd.io/@sysprog/linux2020-homework1) ## 實做 ### q_new() * 特別注意如果 malloc 失敗要回傳 NULL, 後面的 function 每個有使用 malloc 的都要記得處理 * 以前寫程式都沒有這樣做,不太安全 ```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_free() * 記得要 free 掉分配的記憶體空間 ```cpp= void q_free(queue_t *q) { if (!q) return; if (q->size > 0) { list_ele_t *curr = q->head; list_ele_t *prev = NULL; while (curr) { prev = curr; curr = curr->next; free(prev->value); free(prev); } } free(q); } ``` ### q_insert_head() * 要特別處理 queue 是 empty ,第一次新增 node 的情況 ```cpp= bool q_insert_head(queue_t *q, char *s) { if (!q) { return false; } list_ele_t *newh; newh = malloc(sizeof(list_ele_t)); if (!newh) { return false; } newh->value = malloc((strlen(s) + 1) * sizeof(char)); if (!newh->value) { free(newh); return false; } memset(newh->value, '\0', strlen(s) + 1); strncpy(newh->value, s, strlen(s)); if (!q->head) { q->tail = newh; } newh->next = q->head; q->head = newh; q->size++; return true; } ``` ### q_insert_tail * 和 q_insert_head 類似 * 因為要達到 $O(1)$ 的時間複雜度,不能使用從 head iterate 到最後一個 node 的方式新增,因此在 queue_t 內新增了 tail 的變數來紀錄最末端的 node * 遇到的問題 * 宣告出 newt 之後沒有把 newt->next 初始化為 NULL(下面21行),所以一直 segmetation fault * 對 valgrind 還不太熟悉,同一種錯誤信息 `8 bytes in 1 blocks are still reachable in loss record` 出現很給多個,不確定要以哪個為基準,有的說是在 qtest 等其他檔案,沒有寫道是 queue.c 裡面的,有的說是測試過沒有問題的 function 內,花了好久才找出來。 ```cpp= bool q_insert_tail(queue_t *q, char *s) { if (!q) { return false; } list_ele_t *newt; newt = malloc(sizeof(list_ele_t)); if (!newt) { return false; } newt->value = malloc((strlen(s) + 1) * sizeof(char)); if (!newt->value) { free(newt); return false; } memset(newt->value, '\0', strlen(s) + 1); strncpy(newt->value, s, strlen(s)); newt->next = NULL; if (!q->head) { q->head = newt; } else { q->tail->next = newt; } q->tail = newt; q->size++; return true; } ``` * valgrind 部分錯誤訊息 ``` ==2335== 8 bytes in 1 blocks are still reachable in loss record 1 of 34 ==2335== at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==2335== by 0x10CC19: q_insert_head (queue.c:63) ==2335== by 0x10A829: do_insert_head (qtest.c:212) ==2335== by 0x10B992: interpret_cmda (console.c:220) ==2335== by 0x10BF06: interpret_cmd (console.c:243) ==2335== by 0x10C4D4: cmd_select (console.c:569) ==2335== by 0x10C71C: run_console (console.c:628) ==2335== by 0x10AE41: main (qtest.c:772) ``` ### q_remove_head() * 需特別處理只有一個 node 的情況,把 queue 的 head, tail 等設為 NULL。 ```cpp= bool q_remove_head(queue_t *q, char *sp, size_t bufsize) { if (!q || !q->head) { return false; } if (sp != NULL) { memset(sp, '\0', bufsize); strncpy(sp, q->head->value, bufsize - 1); } if (!q->head->next) { free(q->head->value); free(q->head); q->head = NULL; q->tail = NULL; q->size--; return true; } list_ele_t *tmp; tmp = q->head; q->head = q->head->next; q->size--; free(tmp->value); free(tmp); return true; } ``` #### Warning * Warining in q_remove_head() * 嘗試了一陣子之後,發現把 `!sp` 改成 `sp != NULL` 就好了 ```$ queue.c:127:17: warning: Either the condition '!sp' is redundant or there is possible null pointer dereference: sp. [nullPointerRedundantCheck] strncpy(sp, q->head->value, bufsize - 1); ^ queue.c:125:9: note: Assuming that condition '!sp' is not redundant if (!sp) { ^ queue.c:127:17: note: Null pointer dereference strncpy(sp, q->head->value, bufsize - 1); ^ queue.c:127:17: error: Null pointer dereference [nullPointer] strncpy(sp, q->head->value, bufsize - 1); ^ Fail to pass static analysis. ``` * Before: ==!sp== ```cpp if (!sp) { memset(sp, '\0', bufsize); strncpy(sp, q->head->value, bufsize - 1); } ``` * Fixed: ==sp != NULL== * 嘗試了一陣子之後,發現把判斷是否為 NULL 的條件式從 `!sp` 改成 `sp != NULL` 就沒有 warning 了 * 但還沒有很確定為什麼其他地方都可以這樣用 ```cpp if (sp != NULL) { memset(sp, '\0', bufsize); strncpy(sp, q->head->value, bufsize - 1); } ``` ### q_size() * 為了達到 $O(1)$ 的時間複雜度,因此在`queue_t` 加一個變數 `size` 來記錄 linked list 的長度,並在每次 insert, remove node 時更新。 ```cpp= int q_size(queue_t *q) { if (!q) { return 0; } return q->size; } ``` ### q_reverse() * 慢慢從原本的 head 到 tail iterate, 並紀錄前一個 node(prev),做為現在 iterate 到的 node(curr) 的 next(curr->next),來完成 reverse ```cpp= void q_reverse(queue_t *q) { if (!q || !q->head) { return; } list_ele_t *curr = q->head; list_ele_t *prev = NULL; list_ele_t *next; while (curr != NULL) { // Reverse curr->next next = curr->next; curr->next = prev; // Shift one node prev = curr; curr = next; } list_ele_t *tmp = q->tail; q->tail = q->head; q->head = tmp; } ``` ### q_sort() * 使用 merge sort 演算法,並分成 q_sort(),merge_sort(), merge() 等三個 function #### q_sort ```cpp= void q_sort(queue_t *q) { if (!q || q->size == 0 || q->size == 1) { return; } q->head = merge_sort(q->head); // Update list tail list_ele_t *tmp = q->head; while (tmp->next != NULL) { tmp = tmp->next; } q->tail = tmp; } ``` #### merge_sort * 將 linked list 分成兩段的方法 * 利用 `fast` 和 `slow` 這兩個 pointer,來 iterate linked list。而 `fast` 每次前進兩個 node, `slow` 只前進一次,如此一來當 `fast` iterate 完的時候,slow 落在 linked list 的中間。 * 要記得把 slow->next 設為 NULL,左半邊那段 list 知道他切到哪裡(21行) ```cpp= list_ele_t *merge_sort(list_ele_t *head) { if (!head || !head->next) { return head; } /* * Divide nodes of linked list into two halves by slow-fast pointer. * Forward 'fast' by two nodes, and forward 'slow' by one node. */ list_ele_t *fast = head->next; list_ele_t *slow = head; while (fast != NULL && fast->next != NULL) { fast = fast->next->next; slow = slow->next; } list_ele_t *left = head; list_ele_t *right = slow->next; slow->next = NULL; // To let 'right' know where to end left = merge_sort(left); right = merge_sort(right); return merge(left, right); } ``` #### merge * 如果左右有任一是空的話,直接回傳另一邊的 list * 否則就 iterate 一一去比較兩邊的 node 的 value 大小,一個個把比較小的放到 result list 內。 * 因為一開始 result_head 是空的,所以記得要先初始化(13行) * 最後如果有任一一邊還有剩下的,整段接到 result list 上面。 ```cpp= list_ele_t *merge(list_ele_t *left, list_ele_t *right) { if (!left) { return right; } if (!right) { return left; } list_ele_t *result_head; list_ele_t *curr; // Initialize result_head and curr if (strnatcmp(left->value, right->value) < 0) { result_head = curr = left; left = left->next; } else { result_head = curr = right; right = right->next; } while (left != NULL && right != NULL) { if (strnatcmp(left->value, right->value) < 0) { curr->next = left; curr = curr->next; left = left->next; } else { curr->next = right; curr = curr->next; right = right->next; } } // If there is still node left in 'left list' if (left) { curr->next = left; } // If there is still node left in 'right list' if (right) { curr->next = right; } return result_head; } ``` ### natural sort * 把 `strnatcmp.h`, `strnatcmp.c` 下載下來並放到專案內 * 更改 queue.c * 加上 `#include strnatcmp.h, strnatcmp.c ` * 把 strcmp() 改成 strnatcmp() * 更改 make file * 在 `OBJS` 加上 `strnatcmp.o` ``` OBJS := qtest.o report.o console.o harness.o queue.o strnatcmp.o\ random.o dudect/constant.o dudect/fixture.o dudect/ttest.o ``` #### 在 commit 自動檢查時,有一些 warning,大概分為兩個 * The scope of the variable can be reduced * 本來 ca, cb 在 while 外面宣告,但只有在 while loop 內才會用到,故可以在 while 的 scope 宣告就好 * The function is never used. * 把沒用到的 strnatcasecmp() 整段刪除 ``` strnatcmp.c:109:14: style: The scope of the variable 'ca' can be reduced. [variableScope] nat_char ca, cb; ^ strnatcmp.c:109:18: style: The scope of the variable 'cb' can be reduced. [variableScope] nat_char ca, cb; ^ strnatcmp.c:167:0: style: The function 'strnatcasecmp' is never used. [unusedFunction] ^ Fail to pass static analysis. ```