# 2020q1 Homework1 (lab0) contributed by < `shihtiy` > ## 目標 - 使用 linked list 來實作 queue 的 operation - [github: lab0](https://github.com/shihtiy-tw/lab0-c) - [H01: lab0](https://hackmd.io/@sysprog/linux2020-lab0) - [2020q1 Homework1 (作業區)](https://hackmd.io/@sysprog/linux2020-homework1) <s> 大綱 - Implementation - 檢討與討論 </s> :::danger 不需要特別去撰寫大綱,HackMD 可自動建立 :notes: jserv ::: ## 開發環境 - OS ```shell $ lsb_release -a LSB Version: core-9.20170808ubuntu1-noarch:security-9.20170808ubuntu1-noarch Distributor ID: Ubuntu Description: Ubuntu 18.04.2 LTS Release: 18.04 Codename: bionic ``` - Compiler ```shell $ gcc --version gcc (Ubuntu 8.3.0-6ubuntu1~18.04.1) 8.3.0 ``` ## Programming Tasks 完成以下的 oprations: - q_new: Create a new, empty queue. - q_free: Free all storage used by a queue. - q_insert head: Attempt to insert a new element at the head of the queue (LIFO discipline). - q_insert tail: Attempt to insert a new element at the tail of the queue (FIFO discipline). - q_remove head: Attempt to remove the element at the head of the queue. - q_size: Compute the number of elements in the queue - **q_sort: Sort elements of queue in ascending order** > q_sort 目前還在卡關中... [name=Stanley Yuan] # Implementation ## queue_t 因應題目要求 q_insert_tail 和 q_size 時間複雜度爲 O(1),再 queue_t 中加上指向 queue 尾端的 pointer tail 和記錄大小的 size; ```c typedef struct { list_ele_t *head; /* Linked list of elements */ list_ele_t *tail; int size; } queue_t; ``` ## queue_new 這邊需要特別注意的是 malloc 執行失敗的 case,如果 malloc 執行失敗就會 return NULL[[1]](https://linux.die.net/man/3/malloc)。 ```cpp queue_t *q_new() { queue_t *q = malloc(sizeof(queue_t)); if (q) { q->head = NULL; q->tail = NULL; q->size = q_size(q); return q; } return NULL; } ``` ## q_free 使用一個 global variable tmp 來記錄要被 free 的 element。這個 tmp 還會被其他 function 使用。 ```cpp= list_ele_t *tmp; void q_free(queue_t *q) { if (q) { if (q->head) { while (q->head != NULL) { tmp = q->head; q->head = q->head->next; free(tmp->value); free(tmp); } } free(q); } } ``` ## q_insert_head 這邊我遇到兩個問題: 第一個是不可以使用 strcpy,用 snprintf 定義一個 strlcpy 來做限定長度的 string copy。 第二個是我對 string 和 pointer 的理解錯誤,見 [string size 檢討](https://hackmd.io/pgebahqjT4S07kkO3xK7WA#string-size-%E6%AA%A2%E8%A8%8E)。 ```cpp #ifndef strlcpy #define strlcpy(dst, src, sz) snprintf((dst), (sz), "%s", (src)) #endif bool q_insert_head(queue_t *q, char *s) { if (q) { list_ele_t *newh; newh = malloc(sizeof(list_ele_t)); int s_size = strlen(s) + sizeof(char); if (newh) { newh->next = q->head; q->head = newh; newh->value = malloc(s_size); if (newh->value) { /*use strlcpy instead*/ strlcpy(newh->value, s, s_size); q->size += 1; if (q->size == 1) q->tail = q->head; return true; } } } return false; } ``` ## q_insert_tail 這裏使用在 queue_t 中定義的 tail 實作,要注意的是我之前一直沒有把 newl->value malloc 失敗的時候 free 而一直失敗。加上 free(newl->value); 後才成功。 ```cpp bool q_insert_tail(queue_t *q, char *s) { if (q) { list_ele_t *newl; newl = malloc(sizeof(list_ele_t)); int s_size = strlen(s) + sizeof(char); if (newl) { newl->next = NULL; newl->value = malloc(s_size); if (newl->value) { if (q->tail) { q->tail->next = newl; } q->tail = newl; /*use strlcpy instead*/ strlcpy(newl->value, s, s_size); q->size += 1; if (q->size == 1) q->head = q->tail; return true; } free(newl->value); } free(newl); } return false; } ``` ## q_remove_head ```cpp bool q_remove_head(queue_t *q, char *sp, size_t bufsize) { if (q && q->head) { if (sp) { /*using strlcpy instead*/ strlcpy(sp, q->head->value, bufsize); } tmp = q->head; q->head = q->head->next; q->size -= 1; if (!(q->head)) q->tail = q->head; free(tmp->value); free(tmp); return true; } return false; } ``` ## q_reverse 這裏用兩個 pointer headl 和 headr 來輔助 q->head 記錄上一個 element 的位址和下一個 element 的位址來做 reverse 的動作。 ```cpp void q_reverse(queue_t *q) { if (q && q->head && q->head != q->tail) { list_ele_t *headl = NULL, *headr = q->head->next; q->tail = q->head; while (headr) { q->head->next = headl; headl = q->head; q->head = headr; headr = q->head->next; } q->head->next = headl; } } ``` ## q_size ```cpp int q_size(queue_t *q) { if (q && q->head) return q->size; return 0; } ``` ## q_sort 這裏本來寫不出來,是在答對 quiz 1 後移植過來。但遇到 performance 的問題。目前還沒解決... ```cpp list_ele_t *merge_sort(list_ele_t *start) { if (!start || !start->next) return start; list_ele_t *left = start; list_ele_t *right = left->next; left->next = NULL; left = merge_sort(left); right = merge_sort(right); for (list_ele_t *merge = NULL; left || right;) { if (!right || (left && (strcasecmp(left->value, right->value) < 0))) { if (!merge) { start = merge = left; } else { merge->next = left; merge = merge->next; } left = left->next; } else { if (!merge) { start = merge = right; } else { merge->next = right; merge = merge->next; } right = right->next; } } return start; } /* * 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(queue_t *q) { if (q && q->head && q->head != q->tail) { q->head = merge_sort(q->head); q->tail = q->head; while (q->tail->next != NULL) { q->tail = q->tail->next; } } } ``` # 檢討與討論 ## string size 檢討 我用 snprintf 定義一個 strlcpy 來做限定長度的 string copy。但我原本的適用 sizeof(s) 來算我的 string 長度,因爲用 sizeof 會把結尾的 NULL 字元也算進去。所以原本 s_size 的程式碼是: ```cpp int s_size = sizeof(s); ``` 但一直出錯,這時我也沒有細究,我就把他改成: ```cpp int s_size = sizeof(s) * sizeof(char *); ``` 就過了,我就不追究了。但後來和別人討論時發現我這個寫法不夠精確。也發現我的理解有錯誤。我用以下程式碼測試: ```cpp char greeting[] = "HHelloHelloHelloello"; char *test_string; test_string = malloc(sizeof(char) * (strlen(greeting) + sizeof(char))); strcpy(test_string, greeting); printf("greeting strlen %ld\n", strlen(greeting)); printf("greeting sizeof %ld\n", sizeof(greeting)); printf("test_string strlen %ld\n", strlen(test_string)); printf("test_string sizeof %ld\n", sizeof(test_string)); // output // greeting strlen 20 // greeting sizeof 21 // test_string strlen 20 // test_string sizeof 8 ``` sizeof 確實回傳 greeting 包含 NULL 字元的字串長度,而把 greeting copy 到 test_string 後 sizeof 卻只有回傳 8 bytes。上網查時看到資料解釋兩者的差異,當宣告 string 的時候會是在記憶體中 stack 的位置,而 malloc 會在 heap 的位值。用 GDB 測試也顯示不同的結果: ```shell (gdb) whatis greeting type = char [21] (gdb) whatis test_string type = char * ``` 但如果把 greeting 和 test_string 都傳入一個 function 的時候,這時候傳入的都是指向 greeting 和 test_string 的 pointer,用 sizeof 時兩者都會回傳記憶體位址的大小,而不會是 string 包含 NULL 字元的長度。但我目前還沒有找到一個滿意的實證的方式。 我在這個 [commit](https://github.com/shihtiy-tw/lab0-c/commit/b7104682fa38f61de7e5d15b048e43ebf5069e03) 有做了想對應的修正也有照老師說的補上 commit message。 --- https://www.youtube.com/watch?v=scLFY2CRtFo 1:10:00 http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf 6.5.3.2 Address and indirection operators ## q_sort 還在努力中... 按照 qtest 的測資會有 segment fault 的錯誤。 ## 和去年比... 這是第二次挑戰老師的課程,和去年比較掙扎的寫作業的情況比有明顯的不同,遇到問題時會先朝 offical document 或是規格書找答案,也會試着用 GDB 來先自己 debug。