# 2019q3 Homework2 (lab0) contributed by < `Benny1117Yen` > #### tags: `sysprog2019` :::danger 注意看作業要求,對於共筆格式有諸多規範。從細節小處做起! :notes: jserv ::: * [C Programming lab](http://www.cs.cmu.edu/~213/labs/cprogramminglab.pdf) ## 作業目標 CMU [Introduction to Computer Systems (ICS)](http://www.cs.cmu.edu/~213/index.html) 準備了 [C Programming Lab](http://www.cs.cmu.edu/~213/labs/cprogramminglab.pdf) 作為檢閱學生 C 語言程式設計的認知: * Explicit memory management, as required in C. * Creating and manipulating pointer-based data structures. * Working with strings. * Enhancing the performance of key operations by storing redundant information in data structures. * Implementing robust code that operates correctly with invalid arguments, including NULL pointers. 實驗目標為實作 queue * first in, first out (FIFO) * last in, first out (LIFO) ## Resources 1. [C programming](https://en.wikibooks.org/wiki/C_Programming) 2. [Linked lists](http://www.cs.cmu.edu/~iliano/courses/18S-CMU-CS122/handouts/10-linkedlist.pdf) * [我對Linked lists的筆記](https://hackmd.io/@P86071244/H1ReZsxur) 3. [Asympotic (big-Oh) notation](http://www.cs.cmu.edu/~iliano/courses/18S-CMU-CS122/handouts/05-sort.pdf) 4. [afcidk 的開發紀錄](https://hackmd.io/@afcidk/ry4VZS9SN) 5. [posutsai 的開發紀錄](https://hackmd.io/@posutsai/HkgKesMYX?type=view) 6. [你所不知道的 C 語言: 指標篇](https://hackmd.io/@sysprog/HyBPr9WGl) ## 實驗總覽 `queue.h` 這檔案含有以下結構宣告: ```cpp= /* Linked list element */ typedef struct ELE { char *value; struct ELE *next; } list_ele_t; ``` ```cpp= /* Queue structure */ typedef struct { list_ele_t *head; /* Linked list of elements */ } queue_t; ``` 如圖所示,將它們組合在一起以實現字符串駐列。駐列的上層表示形式是 `queue_t` 類型的結構。在開始代碼中,此結構僅包含單一個字段 `head`,之後需要變再添加其他字段。駐列內容表示為單鏈接列表,每個元素由類型為 `list_ele_t` 的結構表示,具有字段 `value` 和 `next`,分別存儲指向字符串的指針和指向下一個 `list元素` 的指針。最終列表元素的下一個指針設置為 `NULL`。您可以將其他字段添加到結構 `list_ele` 中,儘管不必這樣做。 ![Fig.1](https://github.com/Benny1117Yen/lab0-c/blob/master/Fig1.png?raw=true) * 圖1:駐列的鍊錶實現。每個列表元素都有一個 `value` 字段,該字段指向一個字符矩陣(C的字符串表示形式),`next` 字段指向下一個列表元素。字符根據 `ASCII` 編碼(以十六進制顯示)進行編碼。 回想一下,C 語言裡面字符串表示是型態為 `char` 的有值陣列。在大多數機器中,數據類型 `char` 表示為 `1 byte`。為了存儲長度為 `l` 的字符串,該陣列包含 `l + 1` 個元素,前一個 `l` 存儲字符的代碼(通常為 `ASCII1` 格式),最後一個設置為 `0`。列表元素的 `value` 字段是一個指針指到字符的陣列。該圖表示列表 `[“ cab”,“ bead”,“ cab”]` 的表示形式,其中字符 `a–e` 以十六進製表示為 `ASCII` 碼 `61–65`。 觀察字符串 `“cab”` 的兩個實例是如何由分離的陣列們來表示 -- 每個列表元素應具有其字符串的分離副本。 在我們的 `C` 代碼中,駐列是類型為 `queue_t *` 的指針。我們區分兩種特殊情況:`NULL` 駐列是指針值為 `NULL`。空駐列是指向有效結構,但是頭 (head) 字段的值為 `NULL`。所以代碼將需要正確處理這兩種情況,以及包含一個或多個元素的駐列。 ## Programming Task Your task is to modify the code in `queue.h` and `queue.c` to fully implement the following functions. `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`: 以反向次序重新排列列表。這函數不該分配或釋放任何列表元素 (不論是直接地或呼叫其他有分配或釋放列表元素功能的函數。) 也就是說,它只能重排已經存在的元素。 在這兩個文件的註釋中可以找到更多詳細信息,包括如何處理無效的操作(例如,從空駐列或 `NULL` 駐列中刪除),以及函數應具有的副作用和返回值。 對於以字符串作為參數的函數,必須通過調用 `malloc` 分配空間(記住要包含終止字符的空間),然後從原始空間複製到新分配的空間,來創建和存儲字符串的副本。當需要釋放列表元素時,還必須釋放字符串使用的空間。不能假設字符串的長度有任何固定的上限 -- 必須根據字符串的長度為每個字符串分配空間。 其中兩個功能:`q_insert_tail` 和 `q_size` 將需要您付出一些努力才能達到所需的性能標準。原生執行將需要 `O(n)` 個步驟來處理具有 `n` 個元素的駐列。我們要求您的執行在時間 `O(1)` 內進行操作,即無論駐列大小如何,該操作僅需要固定數量的步驟。您可以藉由在 `queue_t` 數據結構中包括 (include) 其他字段並在插入,刪除和反轉列表元素時正確管理這些值來執行。 您的程序將在具有 `1,000,000` 個以上元素的駐列上進行測試。會發現無法使用遞歸函數在如此長的列表上進行操作,因為這將需要過多的堆棧 (stack) 空間。相反,需要使用循環 (loop) 遍歷 (traverse) 列表中的元素。 ## 開發環境 ![開發環境](https://github.com/Benny1117Yen/lab0-c/blob/master/%E9%96%8B%E7%99%BC%E7%92%B0%E5%A2%83.png?raw=true) :::danger 文字訊息 ==不要== 用圖片展現! :notes: jserv ::: ## 實作記錄 ### `queue.h` 依照內容中的註解需求,新增以下程式碼 ```cpp typedef struct { list_ele_t *head; /* Linked list of elements */ /* You will need to add more fields to this structure to efficiently implement q_size and q_insert_tail */ list_ele_t *tail; /* Add tail pointer in the queue */ int size; /* Queue size */ } queue_t; ``` * 新增`list_ele_t *tail` 指向 `tail` 列表元素 * 新增 `int size` 為 queue 的大小 ### `queue.c` #### `queue_t *q_new()` 這是初始化的步驟 ```cpp queue_t *q_new() { queue_t *q = malloc(sizeof(queue_t)); /* What if malloc returned NULL? */ if (q = NULL) { return NULL; } q->head = NULL; q->tail = NULL; q->size = 0; return q; } ``` * 創造一個頭、尾、大小都為 `NULL` 的駐列,若不能分配空間時,回傳 `NULL`。 #### `void q_free(queue_t *q)` ```cpp= void q_free(queue_t *q) { /* How about freeing the list elements and the strings? */ if (q == NULL) return; list_ele_t *ptr = q->head; list_ele_t *ptr = NULL; while (ptr) { prev = ptr; ptr = ptr->next; free(prev->value); free(prev); } /* Free queue structure */ free(q); } ``` * if 條件是判斷駐列為 `NULL` 時,指針指到 `head`,指針的前一個位子為 `NULL`。 * while 是當 `ptr` 有值 (指到頭),那麼 `prev` 就等於 `ptr` ,`ptr` 去指向 `next`。 * 如此,只用 `ptr` 指標就能指到要刪除的元素,要記得釋放指針指到的值記憶體大小跟指針本身記憶體大小。直到整個駐列清空。 * `free(q)` 釋放駐列所占記憶體大小。 #### `q_insert_head` ```cpp= bool q_insert_head(queue_t *q, char *s) { /* What should you do if the q is NULL? */ if (q == NULL) return false; list_ele_t *newh; newh = malloc(sizeof(list_ele_t)); /* Allocated failed */ if (newh == NULL) return false; /* Don't forget to allocate space for the string and copy it */ char *value = malloc((strlen(s) + 1) * sizeof(char)); if (value == NULL) { free(newh); return false; } strcpy(value, s); newh->value = value; /* What if either call to malloc returns NULL? */ newh->next = q->head; if (q->size == 1) q->tail = newh; q->head = newh; q->size++; return true; } ``` * 若駐列不為空,我們創造一新指針,並用 `malloc` 配置一個空間給它。 * 再用 `strcpy` 把值 copy 給 `value`,也就是新的駐列的記憶體位址。 * 而後新指針指向新的駐列頭,這裡有個判斷當 `size` 為 `1` 時,駐列尾指向新指針。 * 最後駐列大小加一。 #### `q_inset_tail` ```cpp= bool q_insert_tail(queue_t *q, char *s) { /* You need to write the complete code for this function */ /* Remember: It should operate in O(1) time */ if (q == NULL) return false; list_ele_t *newt; newt = malloc(sizeof(list_ele_t)); /* Allocated failed */ if (newt == NULL) { return false; } char *value = malloc((strlen(s) + 1) * sizeof(char)); if (value == NULL) { free(newt); return false; } strcpy(value, s); newh->value = value; q->tail->next = newt; q->tail = newt; q->tail->next = NULL; if (q->size == 1) q->tail = newt; q->head = newt; q->size++; return true; } ``` * 跟 `q_insert_head` 概念相同,每次執行後 `q->tail` 要後移一個才會指向最後一個值。 ### `q_remove_head` ```cpp= bool q_remove_head(queue_t *q, char *sp, size_t bufsize) { /* You need to fix up this code. */ if (q == NULL || q->size == 0) return false; list_ele_t *tmp = q->head; q->head = q->head->next; int copy_len = bufsize / sizeof(char) - 1; if (sp != NULL) { memcpy(sp, tmp->value, copy_len); sp[copy_len] = '\0'; } free(tmp); q->size--; if (q->size == 0) q->tail = NULL; return true; } ``` * 先用一個 tmp pointer 儲存原始 head 作為後來的 free 以及字串處理使用。 * 更新 q->head 並調整 size。 * 題目要求要複製 bufsize - 1 長度的字串到 sp ,並在字串最後補上節數字元 ‘\0’,我這裡使用的是 memcpy 而非 strdup,memcpy 的第三個參數,表示的是要拷貝的長度,所以我們必須先計算這樣的長度代表幾個 char 並在適當位置補上結束字元。 * 最後在針對 tmp 做釋放。 * 若是在移除 head 之後,list 內沒有任何節點,重複上述所有步驟,`q->head = q->head->next` 並不會受到影響因為會被更新為 NULL。由於現在 list 內沒有任一結點,所以我們也要把 tail 設為 NULL。 ### `q_size` ```clike int q_size(queue_t *q) { /* You need to write the code for this function */ /* Remember: It should operate in O(1) time */ if (q == NULL) return 0; return q->size; } ``` ### `q_reverse` ```clike void q_reverse(queue_t *q) { /* You need to write the code for this function */ if (q == NULL) return false; list_ele_t *tmp = NULL; prev = q->head; while (prev) { tmp = q->head; q->head = q->head->next; free(tmp->value); free(tmp); } free(q); } ``` ## `qtest` 分數 ```cpp= --- Trace Points +++ TESTING trace trace-01-ops: # Test of insert_head and remove_head ERROR: Freed queue, but 3 blocks are still allocated --- trace-01-ops 0/6 +++ TESTING trace trace-02-ops: # Test of insert_head, insert_tail, and remove_head ERROR: Removed value meerkat != expected value gerbil ERROR: Removed value bear != expected value meerkat ERROR: Removed value gerbil != expected value bear ERROR: Segmentation fault occurred. You dereferenced a NULL or invalid pointer ERROR: Removal from queue failed (1 failures total) --- trace-02-ops 0/6 +++ TESTING trace trace-03-ops: # Test of insert_head, insert_tail, reverse, and remove_head ERROR: Removed value gerbil != expected value squirrel ERROR: Removed value vulture != expected value gerbil ERROR: Removed value bear != expected value gerbil ERROR: Removed value gerbil != expected value bear ERROR: Removed value squirrel != expected value dolphin Error limit exceeded. Stopping command execution --- trace-03-ops 0/6 +++ TESTING trace trace-04-ops: # Test of insert_head, insert_tail, and size ERROR: Freed queue, but 6 blocks are still allocated --- trace-04-ops 0/6 +++ TESTING trace trace-05-ops: # Test of insert_head, insert_tail, remove_head reverse, and size ERROR: Removed value gerbil != expected value dolphin ERROR: Removed value bear != expected value gerbil ERROR: Removed value meerkat != expected value bear ERROR: Removed value bear != expected value meerkat ERROR: Segmentation fault occurred. You dereferenced a NULL or invalid pointer ERROR: Removal from queue failed (1 failures total) Error limit exceeded. Stopping command execution --- trace-05-ops 0/6 +++ TESTING trace trace-06-string: # Test of truncated strings ERROR: Removed value aardvark_bear_dolphin_gerbil_jaguar != expected value meerkat_panda_squirrel_vulture_wolf ERROR: Removed value aardvark_bear_dolphin_gerbil_j != expected value meerkat_panda_squirrel_vulture ERROR: Removed value meerkat_panda_squirre != expected value aardvark_bear_dolphin ERROR: Removed value meerkat_ != expected value aardvark ERROR: Removed value meerkat_panda_squirrel_vulture_wolf != expected value aardvark_bear_dolphin_gerbil_jaguar Error limit exceeded. Stopping command execution --- trace-06-string 0/7 +++ TESTING trace trace-07-robust: # Test operations on NULL queue --- trace-07-robust 7/7 +++ TESTING trace trace-08-robust: # Test operations on empty queue --- trace-08-robust 7/7 +++ TESTING trace trace-09-robust: # Test remove_head with NULL argument ERROR: Freed queue, but 1 blocks are still allocated --- trace-09-robust 0/7 +++ TESTING trace trace-10-malloc: # Test of malloc failure on new --- trace-10-malloc 7/7 +++ TESTING trace trace-11-malloc: # Test of malloc failure on insert_head --- trace-11-malloc 7/7 +++ TESTING trace trace-12-malloc: # Test of malloc failure on insert_tail ERROR: Freed queue, but 2 blocks are still allocated --- trace-12-malloc 0/7 +++ TESTING trace trace-13-perf: # Test performance of insert_tail ERROR: Freed queue, but 2 blocks are still allocated --- trace-13-perf 0/7 +++ TESTING trace trace-14-perf: # Test performance of size --- trace-14-perf 7/7 +++ TESTING trace trace-15-perf: # Test performance of insert_tail, size, and reverse ERROR: Freed queue, but 2 blocks are still allocated --- trace-15-perf 0/7 --- TOTAL 35/100 ``` * 分數還有待提高,這部份我還需要在修正。做單元測試有助於檢查函數可不可行,通常會以預期報錯或回傳正確來設計,當中也長用到 `Pseudo code`。 ## `qtest` 技巧 -- signal handler 待補 ## 解析 Valgrind 的運作原理和針對本程式的使用 待補