# 2020q3 Homework1 (lab0) contributed by < `Rsysz` > ###### tags: `sysprog` ## Outline [TOC] ## 環境 ```shell= $ uname -a Linux itlab-right 5.4.0-47-generic #51~18.04.1-Ubuntu SMP Sat Sep 5 14:35:50 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux $ gcc --version gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0 Copyright (C) 2017 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ``` ## 作業要求 * `q_new`: 建立新的「空」佇列; * `q_free`: 釋放佇列所佔用的記憶體; * `q_insert_head`: 在佇列開頭 (head) 加入 (insert) 給定的新元素 (以 LIFO 準則); * `q_insert_tail`: 在佇列尾端 (tail) 加入 (insert) 給定的新元素 (以 FIFO 準則); * `q_remove_head`: 在佇列開頭 (head) 移除 (remove) 給定的元素。 * `q_size`: 計算佇列中的元素數量。 * `q_reverse`: 以反向順序重新排列鏈結串列,該函式不該配置或釋放任何鏈結串列元素,換言之,它只能重排已經存在的元素; * `q_sort`: 以==遞增順序==來排序鏈結串列的元素 ## 實作流程 ### queue.h 根據作業提示 > Return number of elements in queue. Return 0 if q is NULL or empty Remember: It should operate in O(1)time > You will need to add more fields to this structure to efficiently implement `q_size` and `q_insert_tail` 新增 **int size** 與 **list_ele_t * tail** 到 `struct queue_t`: ```cpp= typedef struct { list_ele_t *head; /* Linked list of elements */ list_ele_t *tail; int size; } queue_t; ``` ### queue.c * **q_new** * 初始化`queue_t`前先檢測是否成功分配記憶體空間,避免==Segmentation fault== ```cpp= queue_t *q_new() { queue_t *q = malloc(sizeof(queue_t)); /* DONE: What if malloc returned NULL? */ if (q) { q->head = NULL; q->tail = NULL; q->size = 0; } return q; } ``` * **q_free** * 新增**list_ele_t * tmp**暫存`q->head`指標,遍歷`queue_t`將內部所有記憶體空間釋放 ```cpp= void q_free(queue_t *q) { /* DONE: How about freeing the list elements and the strings? */ /* Free queue structure */ if (!q) return; while (q->head) { list_ele_t *tmp = q->head; q->head = q->head->next; free(tmp->value); free(tmp); } free(q); } ``` * **q_insert_head** * 新增節點`newh`後先行判定有無記憶體空間 * 利用`newh->value = malloc(sizeof(char) * (strlen(s) + 1))`於**list_ele_t * newh**結構內取得記憶體空間後判定有無取得==結構內==記憶體空間 * 使用`strncpy(newh->value, s, strlen(s) + 1)`複製==strlen(s) + 1==長度的字串(含'\0')至`newh->value`的記憶體空間中 1. 將`newh->next`指向`q->head`,後取代原先的`q->head` 2. 當新增的元素為`queue_t`中第一個元素時`q->tail`指向`newh` ```cpp= bool q_insert_head(queue_t *q, char *s) { if (!q) return false; list_ele_t *newh = malloc(sizeof(list_ele_t)); /* DONE: What should you do if the q is NULL? */ if (!newh) return false; newh->value = malloc(sizeof(char) * (strlen(s) + 1)); /*add 1 for '\0'*/ if (!newh->value) { free(newh); return false; } strncpy(newh->value, s, strlen(s) + 1); /* Don't forget to allocate space for the string and copy it */ /* What if either call to malloc returns NULL? */ newh->next = q->head; q->head = newh; if (!q->tail) q->tail = q->head; q->size++; return true; } ``` * **q_insert_tail** * 新增節點`newt`後先行判定有無記憶體空間 * 利用`newt->value = malloc(sizeof(char) * (strlen(s) + 1))`於**list_ele_t * newt**結構內取得記憶體空間後判定有無取得==結構內==記憶體空間 * 使用`strncpy(newh->value, s, strlen(s) + 1)`複製==strlen(s) + 1==長度的字串(含'\0')至`newt->value`的記憶體空間中 1. 當新增的元素為`queue_t`中第一個元素時`q->head`指向`newt` 2. 若非第一個元素將`q->tail->next`指向`newt` 3. 上述兩者執行完後將`q->tail`指向`newt`,更新`q->tail`指標 ```cpp= bool q_insert_tail(queue_t *q, char *s) { /* Remember: It should operate in O(1) time */ if (!q) return false; list_ele_t *newt = malloc(sizeof(list_ele_t)); if (!newt) return false; newt->value = malloc(sizeof(char) * (strlen(s) + 1)); if (!newt->value) { free(newt); return false; } strncpy(newt->value, s, strlen(s) + 1); newt->next = NULL; if (q->tail) q->tail->next = newt; else q->head = newt; q->tail = newt; q->size++; return true; } ``` * **q_remove_head** * 利用**list_ele_t * tmp**暫存`q->head`以便於將待刪除節點內的`value`存入`char *sp` * 當傳入的buffer`sp`不為NULL時,利用snprintf比較`bufsize`與`sp`的大小以確保'\0'的存入,避免==Memory overflow== * 釋放刪除節點記憶體空間,當刪除的節點為最後一個時將`q->tail`指向`NULL` ```cpp= bool q_remove_head(queue_t *q, char *sp, size_t bufsize) { if (!q_size(q)) return false; list_ele_t *tmp = q->head; q->head = q->head->next; if (sp) snprintf(sp, bufsize, "%s", tmp->value); free(tmp->value); free(tmp); q->size--; if (!q_size(q)) q->tail = NULL; return true; } ``` * **q_size** * 原本使用?做三元條件運算,但發現額外的else會導致超時,因此修改為單純if做判斷 ```cpp= int q_size(queue_t *q) { if (!q) return 0; return q->size; } ``` * **q_reverse** * 利用`cursor`遍歷`queue_t`,重定`q->head->next`指標方向 ```cpp= void q_reverse(queue_t *q) { if (!q || q->head == q->tail) // Empty 0 | 1 return; list_ele_t *cursor = NULL; q->tail = q->head; while (q->head) { list_ele_t *next = q->head->next; q->head->next = cursor; cursor = q->head; q->head = next; } q->head = cursor; } ``` * **q_sort** * `q_sort`僅作為副函數接口使用 * 為確保每種情況皆符合$O(nlog(n))$的時間複雜度使用Top-down方法遞迴實現`merge_sort` * 利用原生`strcmp`比較兩字串長度並將較小值放入遞迴中的**list_ele_t * head**進行merge ```cpp= void q_sort(queue_t *q) { if (!q || q->head == q->tail) // Empty 0 | 1 return; q->head = q->tail = merge_sort(q->head); while (q->tail->next) q->tail = q->tail->next; } list_ele_t *merge_sort(list_ele_t *head) { if (!head || !head->next) return head; list_ele_t *l_head = head; list_ele_t *l_tail = head->next; /*split*/ while (l_tail && l_tail->next) { l_head = l_head->next; l_tail = l_tail->next->next; } l_tail = l_head->next; l_head->next = NULL; l_head = head; /*sorting*/ list_ele_t *l_h = merge_sort(l_head); list_ele_t *l_t = merge_sort(l_tail); return merge(l_h, l_t); } list_ele_t *merge(list_ele_t *l_h, list_ele_t *l_t) { list_ele_t *head = NULL; list_ele_t **cur = &head; while (l_h && l_t) { if (strcmp(l_h->value, l_t->value) > 0) { *cur = l_t; l_t = l_t->next; } else { *cur = l_h; l_h = l_h->next; } cur = &((*cur)->next); } *cur = l_t ? l_t : l_h; return head; ``` ### qtest.c * **fill_rand_string** ```cpp= static void fill_rand_string(char *buf, size_t buf_size) { size_t len = 0; if (buf_size < MIN_RANDSTR_LEN) // not gonna happen cuz buf_size is set to // MAX_RADNSTR_LEN buf_size = MAX_RANDSTR_LEN; while (len < MIN_RANDSTR_LEN) len = rand() % buf_size; for (size_t n = 0; n < len; n++) { buf[n] = charset[rand() % (sizeof charset - 1)]; } buf[len] = '\0'; } ``` * **do_sort** * 為了使`make test`正常運行 也修改了 traces 裡面的腳本 將`sort`改為`sort a` * 另外將trace-16-perf內部`sort`與`reverse`整合為`sort d` ```cpp= bool do_sort(int argc, char *argv[]) { if (argc != 2) { report(1, "%s usage:[a/d]", argv[0]); return false; } if (!q) report(3, "Warning: Calling sort on null queue"); error_check(); int cnt = q_size(q); if (cnt < 2) report(3, "Warning: Calling sort on single node"); error_check(); set_noallocate_mode(true); if (exception_setup(true)) q_sort(q); exception_cancel(); set_noallocate_mode(false); if (!strcasecmp(argv[1], "d")) q_reverse(q); bool ok = true; if (q) { for (list_ele_t *e = q->head; e && --cnt; e = e->next) { /* Ensure each element in ascending order */ /* DONE: add an option to specify sorting order */ if (!strcasecmp(argv[1], "a")) { if (strcasecmp(e->value, e->next->value) > 0) { report(1, "ERROR: Not sorted in ascending order"); ok = false; break; } } else { if (strcasecmp(e->value, e->next->value) < 0) { report(1, "ERROR: Not sorted in descending order"); ok = false; break; } } } } show_queue(3); return ok && !error_check(); } ``` ## 疑難排解 **q_size** * 利用 ? 做三元條件運算 * `!q ? 0 : q->size;`相當於`if (!q) {return 0;} else {return q->size;}` * 後來發現這種方法會導致當==無元素==的`queue_t`不斷調用`q_size`時無法符合$O(1)$的時間複雜度的需求 * 然後現在還不是很確定 究竟`if(!q || !q->head)`是省掉的時間 還是佔用的時間多 ```cpp= int q_size(queue_t *q) { return !q ? 0 : q->size; } ``` **q_sort** * 過程中的辛酸血淚待補 ```cpp= void q_sort(queue_t *q) { if (!q || q->head == q->tail) // empty 0 1 return; q->head = q->tail = merge_sort(q->head); while (q->tail->next) q->tail = q->tail->next; } ``` ## Valgrind & Massif * 使用以下指令測試Massif圖像化功能 ```shell= $ valgrind --tool=massif ./qtest -v 3 -f traces/trace-eg.cmd $ ms_print massif.out.[PID] ``` * 沒視窗介面不能使用Massif-Visualizer 可憐阿 ``` -------------------------------------------------------------------------------- Command: ./qtest -v 3 -f traces/trace-eg.cmd Massif arguments: (none) ms_print arguments: massif.out.12119 -------------------------------------------------------------------------------- KB 11.16^ # | : ::@::@::#: | ::@::@::::::::@: @::#:: | ::@::@::::::::@: @::#:: | ::@::@::::::::@: @::#:: | ::@::@::::::::@: @::#:: | ::@::@::::::::@: @::#:: | ::@::@::::::::@: @::#:: | ::@::@::::::::@: @::#:: | ::@::@::::::::@: @::#:: | ::@::@::::::::@: @::#:: | ::@::@::::::::@: @::#:: | ::@::@::::::::@: @::#:: | ::@::@::::::::@: @::#:: | ::@::@::::::::@: @::#:: | ::@::@::::::::@: @::#:: | ::@::@::::::::@: @::#:: | ::@::@::::::::@: @::#:: | ::@::@::::::::@: @::#:: | :@:@::@::::::::@: @::#::@ 0 +----------------------------------------------------------------------->ki 0 312.1 ``` ## 參考資料 * https://hackmd.io/@ZhuMon/lab0-c * https://hackmd.io/@jwang0306/lab0-c * https://npes87184.github.io/2015-09-12-linkedListSort/ * https://wirelessr.gitbooks.io/working-life/content/snprintf_miao_wu_qiong.html ## TODO - [ ] 運用 Valgrind 排除 qtest 實作的記憶體錯誤,並透過 Massif 視覺化 “simulation” 過程中的記憶體使用量,需要設計對應的實驗 - [ ] 研讀論文 [Dude, is my code constant time?](https://eprint.iacr.org/2016/1123.pdf)