# 2020q3 Homework1 (lab0) contributed by < `nelsonlai1` > ## 開發環境 ``` $ uname -a Linux itlab-right 5.4.0-42-generic #46~18.04.1-Ubuntu SMP Fri Jul 10 07:21:24 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. ``` ## function 功能 1. **q_new** : 新增queue 2. **q_free** : 釋放queue中所有記憶體 3. **q_insert_head** : 在queue最前面加入node 4. **q_insert_tail** : 在queue最後面加入node 5. **q_remove_head** : 刪除queue最前方的node,將刪除的元素存入sp中 6. **q_size** : 回傳queue大小 7. **q_reverse** : 將queue順序顛倒 8. **q_sort** : 將queue由小排到大 ## 開發過程 首先依照作業要求中的提示,在**queue.h**中的`struct queue_t`加入 ``` c= list_ele_t *tail; int size; ``` 接著開始修改**queue.c**中的function : ### **q_new** : ``` c= queue_t *q_new() { queue_t *q = malloc(sizeof(queue_t)); if (q == NULL) return NULL; q->head = q->tail = NULL; q->size = 0; return q; } ``` 這個function挺基本的,先分配一個queue_t大小的空間給q,若分配失敗回傳NULL,反之則對q做初始化,將head與tail都指向NULL,size設為0。 ### **q_free** : ``` c= void q_free(queue_t *q) { if (q == NULL) return; while (q->head != NULL) { list_ele_t *tmp = q->head; q->head = q->head->next; free(tmp->value); free(tmp); } free(q); } ``` 首先先確認q不為NULL後,在q不為empty的條件下,先將`q->head`以一個`tmp`儲存,將 `q->head`移動到下一位後,再將原本的`q->head`(tmp)釋放,最後再將q釋放。 如此一來,便能夠從頭開始輪一次後將所有node釋放。 ### **q_insert_head** : ```c= bool q_insert_head(queue_t *q, char *s) { if (q == NULL) return false; list_ele_t *newh; newh = malloc(sizeof(list_ele_t)); if (newh == NULL) return false; newh->value = malloc(sizeof(char) * (strlen(s) + 1)); if (newh->value == NULL) { free(newh); return false; } snprintf(newh->value, strlen(s) + 1, "%s", s); if (q->head != NULL) newh->next = q->head; else { q->tail = newh; newh->next = NULL; } q->head = newh; q->size++; return true; } ``` 前半段(13行前)在對分配記憶體位置以及錯誤做處理,`newh->value`的部分由於"\0"字元的關係要多分配一個char的空間給它。 snprintf這個function在gcc編譯後會先將要被寫入的string(newh->value)清空,再將要寫入的string(s)補"/0"後寫入,非常適合在這次作業中使用。 接著判斷`q->head`是否存在,存在的話將`q->head` assign給`newh->next`,不存在(q為empty)時,`q->tail`也指向`newh`,同時因為`newh`是唯一一個node,故把`newh->next`指向NULL。最後無論如何都將`q->head`指向`newh`,並把`q->size`加一。 ### **q_insert_tail** : ``` c= bool q_insert_tail(queue_t *q, char *s) { if (q == NULL) return false; list_ele_t *newt; newt = malloc(sizeof(list_ele_t)); if (newt == NULL) return false; newt->value = malloc(sizeof(char) * (strlen(s) + 1)); if (newt->value == NULL) { free(newt); return false; } snprintf(newt->value, strlen(s) + 1, "%s", s); newt->next = NULL; if (q->tail != NULL) q->tail->next = newt; else q->head = newt; q->tail = newt; q->size++; return true; } ``` 前面與`q_insert_head`雷同,不再贅述。 後面則先把`newt->next`指向NULL,若存在`q->tail`則將`q->tail->next`指向newt,反之(q為empty)則需要更新`q->head`,與`q_insert_head`類似。 ### **q->remove_head** : ``` c= bool q_remove_head(queue_t *q, char *sp, size_t bufsize) { if (q == NULL || q->head == NULL) return false; if (sp != NULL) snprintf(sp, bufsize, "%s", q->head->value); list_ele_t *rm = q->head; q->head = q->head->next; free(rm->value); free(rm); q->size--; if (q->size == 0) q->tail = NULL; return true; } ``` 首先判斷q是否為NULL或empty,是的話返回false。 再來看sp是否存在,存在時用snprintf來寫入被刪除的值,不存在則不管它。 接著的概念與`q_free`類似,不過只做一次,最後判斷如果q變成empty的話,將`q->tail`指向NULL。 ### **q_size** : ``` c= int q_size(queue_t *q) { if (q == NULL) return 0; return q->size; } ``` 判斷q是否為empty,如果是的話返回0,不是的話返回`q->size`。 這function雖然簡單,但不知為何有時候make test時會判斷為不是O(1)。 ### **q_reverse** : ``` c= { if (q == NULL || q->head == q->tail) return; list_ele_t *cursor = NULL; q->tail = q->head; while (q->head != NULL) { list_ele_t *next = q->head->next; q->head->next = cursor; cursor = q->head; q->head = next; } q->head = cursor; } ``` 這個function是由quiz1改寫而來的。 首先判斷q是否不存在或是element為0或1,若是的話因為無須reverse直接`return`。令一個`cursor`指向NULL,這個`cursor`會一直跟著`q->head`移動,在`q->head`以及reverse前的`q->head`前一項轉換。 while迴圈中,首先令一個`next`儲存原本的`q->head->next`(原本第二項element),將`q->head->next`指向`cursor`(此時`cursor`為NULL,也就是將原本的第一項改為最後一項),將`cursor`指到`q->head`(現在為最後一項),將`q->head`指向`next`(即將成為倒數第二項)。如此一來,最後`cursor`會指向reverse完的第一項,`q->head`則是指向NULL。 最後把`q->head`指向`cursor`。 ### **q_sort** : ``` c= void q_sort(queue_t *q) { if (q == NULL || q->head == q->tail) return; q->head = q->tail = merge_sort(q->head); while (q->tail->next != NULL) q->tail = q->tail->next; } list_ele_t *merge_sort(list_ele_t *head) { if (head == NULL || head->next == NULL) return head; list_ele_t *fast = head->next; list_ele_t *slow = head; while (fast != NULL && fast->next != NULL) { slow = slow->next; fast = fast->next->next; } fast = slow->next; slow->next = NULL; list_ele_t *l1 = merge_sort(head); list_ele_t *l2 = merge_sort(fast); return merge(l1, l2); } list_ele_t *merge(list_ele_t *l1, list_ele_t *l2) { list_ele_t *head = NULL; list_ele_t **tmp = &head; while (l1 != NULL && l2 != NULL) { if (strcmp(l1->value, l2->value) < 0) { *tmp = l1; tmp = &(*tmp)->next; l1 = l1->next; } else { *tmp = l2; tmp = &(*tmp)->next; l2 = l2->next; } } if (l1 != NULL) *tmp = l1; if (l2 != NULL) *tmp = l2; return head; } ``` 由於作業要求O(nlgn),首先會決定要用quicksort或mergesort,但考慮到quicksort若不做處理的話會在worst case時變成O(n^2),且在linked list實作也較麻煩,這裡使用mergesort。 一開始想到既然已經定義`q->size`以及諸多function,於是直接建立額外的queue來進行mergesort,但沒想到在make test時直接失敗了,應該是使用太多的記憶體空間,但又不忍心刪掉,只好放在註解中。 最後只好乖乖~~抄襲~~參考老師提供的[Linked List Sort](https://npes87184.github.io/2015-09-12-linkedListSort/)做改寫。為了recursive需求,另外令兩個function(同時也要在**queue.h**新增) 在`merge_sort`中,先定義終止條件,當元素為0或1時回傳`head`。接著的技巧利用`fast`速度為`slow`的兩倍,當`fast`跑到末端時,`slow`會位於中間,再將`fast`指向`slow->next`變成後半段,而`slow->next`指向NULL來將前半段分割出來。 `merge`中,以一個`head`以及`head`的pointer `tmp`來將`l1`, `l2`整合排序,使用`tmp`的原因是為了不讓`head`位置跑掉。while迴圈內將`l1`為頭的list, `l2`為頭的list做比較,並依序將`*tmp`指向小的元素,最後當一方沒了時,將`*tmp`指向剩餘的一方,最後return `head`時就會是排序好的。 ## Valgrind & Massif 首先輸入 ``` make valgrind ``` 執行Valgrind 接著用 ``` valgrind --tool=massif ./qtest -v 3 -f traces/trace-eg.cmd ``` 執行Massif ``` cmd> cmd> # Demonstration of queue testing framework cmd> # Use help command to see list of commands and options cmd> # Initial queue is NULL. cmd> show q = NULL cmd> # Create empty queue cmd> new q = [] cmd> # Fill it with some values. First at the head cmd> ih dolphin q = [dolphin] cmd> ih bear q = [bear dolphin] cmd> ih gerbil q = [gerbil bear dolphin] cmd> # Now at the tail cmd> it meerkat q = [gerbil bear dolphin meerkat] cmd> it bear q = [gerbil bear dolphin meerkat bear] cmd> # Reverse it cmd> reverse q = [bear meerkat dolphin bear gerbil] cmd> # See how long it is cmd> size Queue size = 5 q = [bear meerkat dolphin bear gerbil] cmd> # Delete queue. Goes back to a NULL queue. cmd> free q = NULL cmd> # Exit program cmd> quit Freeing queue ``` 再輸入 ``` ms_print massif.out.[數字](輸出的檔案名) ``` 來看massif分析結果 ``` -------------------------------------------------------------------------------- Command: ./qtest -v 3 -f traces/trace-eg.cmd Massif arguments: (none) ms_print arguments: massif.out.14570 -------------------------------------------------------------------------------- KB 11.16^ # | : :::@:@::#: | ::@::@:::::::: @ @::#:: | ::@::@:::::::: @ @::#:: | ::@::@:::::::: @ @::#:: | ::@::@:::::::: @ @::#:: | ::@::@:::::::: @ @::#:: | ::@::@:::::::: @ @::#:: | ::@::@:::::::: @ @::#:: | ::@::@:::::::: @ @::#:: | ::@::@:::::::: @ @::#:: | ::@::@:::::::: @ @::#:: | ::@::@:::::::: @ @::#:: | ::@::@:::::::: @ @::#:: | ::@::@:::::::: @ @::#:: | ::@::@:::::::: @ @::#:: | ::@::@:::::::: @ @::#:: | ::@::@:::::::: @ @::#:: | ::@::@:::::::: @ @::#:: | @::@::@:::::::: @ @::#::@ 0 +----------------------------------------------------------------------->ki 0 314.8 Number of snapshots: 56 Detailed snapshots: [4, 10, 18, 37, 41, 47 (peak), 54] ``` 詳細的記憶體使用狀況,這裡只列出一部分 ``` -------------------------------------------------------------------------------- n time(i) total(B) useful-heap(B) extra-heap(B) stacks(B) -------------------------------------------------------------------------------- 0 0 0 0 0 0 1 209,271 40 32 8 0 2 211,027 544 416 128 0 3 212,471 704 544 160 0 4 214,104 824 640 184 0 77.67% (640B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc. ->77.67% (640B) 0x10B4CF: malloc_or_fail (report.c:189) ->58.25% (480B) 0x10BFE1: add_cmd (console.c:123) | ->03.88% (32B) 0x10AD05: main (qtest.c:82) | | | ->03.88% (32B) 0x10AD1F: main (qtest.c:83) | | | ->03.88% (32B) 0x10AD39: main (qtest.c:84) | | | ->03.88% (32B) 0x10AD53: main (qtest.c:87) | | | ->03.88% (32B) 0x10AD6D: main (qtest.c:90) | | | ->03.88% (32B) 0x10AD87: main (qtest.c:93) | | | ->03.88% (32B) 0x10ADA1: main (qtest.c:96) | | | ->03.88% (32B) 0x10ADBB: main (qtest.c:97) | | | ->03.88% (32B) 0x10C0C2: init_cmd (console.c:93) | | ->03.88% (32B) 0x10ACEB: main (qtest.c:759) | | | ->03.88% (32B) 0x10C0DC: init_cmd (console.c:94) | | ->03.88% (32B) 0x10ACEB: main (qtest.c:759) | | | ->03.88% (32B) 0x10C0F6: init_cmd (console.c:96) | | ->03.88% (32B) 0x10ACEB: main (qtest.c:759) | | | ->03.88% (32B) 0x10C110: init_cmd (console.c:97) | | ->03.88% (32B) 0x10ACEB: main (qtest.c:759) | | | ->03.88% (32B) 0x10C12A: init_cmd (console.c:99) | | ->03.88% (32B) 0x10ACEB: main (qtest.c:759) | | | ->03.88% (32B) 0x10C144: init_cmd (console.c:100) | | ->03.88% (32B) 0x10ACEB: main (qtest.c:759) | | | ->03.88% (32B) 0x10C15E: init_cmd (console.c:101) | ->03.88% (32B) 0x10ACEB: main (qtest.c:759) | ->19.42% (160B) 0x10C057: add_param (console.c:144) ->04.85% (40B) 0x10C17D: init_cmd (console.c:102) | ->04.85% (40B) 0x10ACEB: main (qtest.c:759) | ->04.85% (40B) 0x10C19C: init_cmd (console.c:104) | ->04.85% (40B) 0x10ACEB: main (qtest.c:759) | ->04.85% (40B) 0x10C1BB: init_cmd (console.c:105) | ->04.85% (40B) 0x10ACEB: main (qtest.c:759) | ->04.85% (40B) 0x10C1DA: init_cmd (console.c:106) ->04.85% (40B) 0x10ACEB: main (qtest.c:759) ```