--- tags: linux2022 --- # 2022q1 Homework1 (lab0) contributed by < `ChenBoSyun` > ## 實驗環境 ```shell $ gcc --version gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0 $ lscpu 架構: x86_64 CPU 作業模式: 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 每核心執行緒數: 1 每通訊端核心數: 8 Socket(s): 1 NUMA 節點: 1 供應商識別號: GenuineIntel CPU 家族: 6 型號: 158 Model name: Intel(R) Core(TM) i7-9700 CPU @ 3.00GHz 製程: 13 CPU MHz: 3000.000 CPU max MHz: 4700.0000 CPU min MHz: 800.0000 BogoMIPS: 6000.00 虛擬: VT-x L1d 快取: 256 KiB L1i 快取: 256 KiB L2 快取: 2 MiB L3 快取: 12 MiB NUMA node0 CPU(s): 0-7 ``` ## 實作佇列操作 ### q_new * q_new 初始化一個空的佇列,並且配置一段 `struct list_head` 的空間。此外,q_new 回傳的 `struct list_head*` 只是 linked list 的進入點,因此不需要配置 `element` 空間 ```c struct list_head *q_new() { struct list_head *head = (struct list_head *) malloc(sizeof(struct list_head)); if (!head) return NULL; INIT_LIST_HEAD(head); return head; } ``` ### q_free * 為了遍歷 linked list,我先用 tmp 暫存 node,直到 node 指到下一個 node,才釋放 tmp node * 參考同學的課堂問答,改成用 list API 來實作 * 從 C99 規格書上確認 `free(NULL)` 不會有問題 > The free function causes the space pointed to by ptr to be deallocated, that is, made available for further allocation. If ptr is a null pointer, no action occurs. - ISO/IEC 9899:1999 7.20.3.1 原先的版本 ```c void q_free(struct list_head *l) { if (!l) return; struct list_head *node = l->next; while (node != l) { struct list_head *tmp = node; node = node->next; element_t *ele = list_entry(tmp, element_t, list); free(ele->value); free(ele); } free(l); return; } ``` 使用 list API ```c void q_free(struct list_head *l) { if (!l) return; element_t *ele = NULL, *safe = NULL; list_for_each_entry_safe(ele, safe, l, list) { free(ele->value); free(ele); } free(l); return; } ``` ### q_insert_head * 若是配置字串的記憶體失敗,要記得把 element 也釋放掉 * `strcpy` 有潛在的 overflow 問題,這邊使用 `strncpy`。 * `strlen` 回傳字串本身的長度,還需要加上 null terminator ('\0') ```c bool q_insert_head(struct list_head *head, char *s) { if (!head) return false; element_t *new_ele = (element_t *) malloc(sizeof(element_t)); if (!new_ele) return false; new_ele->value = (char *) malloc(sizeof(char) * (strlen(s) + 1)); if (!new_ele->value) { free(new_ele); return false; } strncpy(new_ele->value, s, strlen(s) + 1); list_add(&new_ele->list, head); return true; } ``` ### q_insert_tail * 步驟與 `q_insert_head` 幾乎相同,只差在最後呼叫 `list_add_tail` ```c bool q_insert_tail(struct list_head *head, char *s) { if (!head) return false; element_t *new_ele = (element_t *) malloc(sizeof(element_t)); if (!new_ele) return false; new_ele->value = (char *) malloc(sizeof(char) * (strlen(s) + 1)); if (!new_ele->value) { free(new_ele); return false; } strncpy(new_ele->value, s, strlen(s) + 1); list_add_tail(&new_ele->list, head); return true; } ``` ### q_remove_head ```c element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize) { if (!head || list_empty(head)) return NULL; element_t *ele = list_first_entry(head, element_t, list); if (sp) { if (strlen(ele->value) + 1 <= bufsize) strncpy(sp, ele->value, strlen(ele->value) + 1); else { strncpy(sp, ele->value, bufsize - 1); *(sp + bufsize - 1) = '\0'; } } list_del(&ele->list); return ele; } ``` ### q_remove_tail ```c element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize) { if (!head || list_empty(head)) return NULL; element_t *ele = list_last_entry(head, element_t, list); if (sp) { if (strlen(ele->value) + 1 <= bufsize) strncpy(sp, ele->value, strlen(ele->value) + 1); else { strncpy(sp, ele->value, bufsize - 1); *(sp + bufsize - 1) = '\0'; } } list_del(&ele->list); return ele; } ``` ### q_size ```c int q_size(struct list_head *head) { if (!head || list_empty(head)) return 0; int num = 0; struct list_head *node; list_for_each (node, head) { num++; } return num; } ``` ### q_delete_mid * 用 fast 走兩步,slow 走一步的方式找到中間點 ```c bool q_delete_mid(struct list_head *head) { if (!head || list_empty(head)) return false; struct list_head *fast = head->next, *slow = head->next; while (fast != head && fast->next != head) { slow = slow->next; fast = fast->next->next; } list_del(slow); element_t *ele = list_entry(slow, element_t, list); free(ele->value); free(ele); return true; } ``` ### q_delete_dup * `q_delete_dup` 有指明是在 `sort` 後呼叫,因此只要檢查 prev 跟 curr 是否相同 :::danger 原先下述的程式碼可以通過 qtest,但是 fetch lab0-c 後,無法通過 qtest 從 @kdnvt 同學提交的 [commit](https://github.com/sysprog21/lab0-c/commit/6edd0f02d1c659ed58cd35ddd987ccb5cafd7cdd) 得知,我誤會了 q_delete_dup 的意思,應該要把所有 duplicate 的字串都刪除,而且剛好原先的 qtest 沒有檢查到 ::: ```c bool q_delete_dup(struct list_head *head) { if (!head) return false; if (list_empty(head) || list_is_singular(head)) return true; struct list_head *prev = head->next, *curr = head->next->next; while (curr != head) { element_t *ele_prev = list_entry(prev, element_t, list); element_t *ele_curr = list_entry(curr, element_t, list); if (strcmp(ele_prev->value, ele_curr->value) == 0) { list_del(prev); free(ele_prev->value); free(ele_prev); } prev = curr; curr = curr->next; } return true; } ``` 正確的 q_delete_dup ```c bool q_delete_dup(struct list_head *head) { if (!head) return false; if (list_empty(head) || list_is_singular(head)) return true; struct list_head *prev = head->next, *curr = head->next->next; bool is_dup = false; while (curr != head) { element_t *ele_prev = list_entry(prev, element_t, list); element_t *ele_curr = list_entry(curr, element_t, list); if (!strcmp(ele_prev->value, ele_curr->value)) { is_dup = true; list_del(prev); free(ele_prev->value); free(ele_prev); } else if (is_dup) { is_dup = false; list_del(prev); free(ele_prev->value); free(ele_prev); } if (is_dup && curr->next == head) { list_del(curr); free(ele_curr->value); free(ele_curr); break; } prev = curr; curr = curr->next; } return true; } ``` ### q_swap * 實作的想法是: 在 node 為奇數時,移除 node->next,再把 node->next 接到 node 前面 * 迴圈結束條件需要加上 `node->next != head`,避免遇到奇數個 node 的情況 ```c void q_swap(struct list_head *head) { if (!head || list_empty(head)) return; struct list_head *node = head->next; while (node != head && node->next != head) { struct list_head *next = node->next; list_del(next); list_add_tail(next, node); node = node->next; } } ``` ### q_reverse ```c void q_reverse(struct list_head *head) { if (!head || list_empty(head)) return; struct list_head *curr = head, *prev; do { prev = curr->prev; curr->prev = curr->next; curr->next = prev; curr = curr->prev; } while (curr != head); } ``` ### q_sort * 首先為了判斷佇列的結尾,我將原本的 circular linked list 改成 Singly linked list * 我採用遞迴方式的 merge sort 來實作 q_sort,分成三個部份 1. merge_list: 合併兩個排序過的佇列,這部份有用到"指標的指標"技巧,不需要額外判斷是否為 linked list 開頭 2. mergesort: 遞迴的主體 3. q_sort: 呼叫 mergesort,最後要把 prev 接回去 ```c static struct list_head *merge_list(struct list_head *l1, struct list_head *l2) { struct list_head **head_ptr, *head = NULL; head_ptr = &head; while (l1 && l2) { element_t *ele_1 = list_entry(l1, element_t, list); element_t *ele_2 = list_entry(l2, element_t, list); if (strcmp(ele_1->value, ele_2->value) < 0) { *head_ptr = l1; l1 = l1->next; } else { *head_ptr = l2; l2 = l2->next; } head_ptr = &(*head_ptr)->next; } *head_ptr = (struct list_head *) ((uintptr_t) l1 | (uintptr_t) l2); return head; } static struct list_head *mergesort(struct list_head *head) { if (!head || !head->next) { return head; } struct list_head *fast = head->next, *slow = head; struct list_head *l1, *l2; while (fast && fast->next) { slow = slow->next; fast = fast->next->next; } fast = slow->next; slow->next = NULL; l1 = mergesort(head); l2 = mergesort(fast); return merge_list(l1, l2); } /* * Sort elements of queue in ascending order * No effect if q is NULL or empty. In addition, if q has only one * element, do nothing. */ 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 *curr = head->next, *prev = head; while (curr) { curr->prev = prev; curr = curr->next; prev = prev->next; } prev->next = head; head->prev = prev; } ```