# 2018q3 Homework3(list) contributed by < `type59ty` > ###### tags: `sysprog2018`, `hw3`, `list` --- ## Linux 風格的 linked list - [x] 閱讀 [你所不知道的C語言: linked list 和非連續記憶體操作](https://hackmd.io/s/SkE33UTHf) 共筆和觀看解說錄影。 - [ ] 自我檢查事項: #### Q1: 為何 Linux 採用 macro 來實作 linked list?一般的 function call 有何成本? - 參考:[直播錄影 1:31:30](https://youtu.be/0B0GNPv02zg?t=5490) 基於 ADT (Abstrate data type) 、資料封裝的概念,因為 linked list 的實做很多變 ( singly, doubly, circular linked list ) ,使用 macro 可以讓使用者不必去理解他的資料型態、實做方式,只要知道怎麼用他的 api 就好 - [macro vs function](https://stackoverflow.com/questions/9104568/macro-vs-function-in-c) | features | Macro | Function | | -------- | -------- | -------- | | 何時被編譯 | Preprocess 階段 | Compile 階段 | | 型態確認 | not done | done | | 程式碼長度 | 增加 | 不變 | | 執行速度 | 較快 | 較慢 | | 編譯錯誤 | not check | check | | 適用於 | 多次出現且長度短的程式 | 多次出現且長度長的程式 | - 相關資料 - [Linux Device Drivers 3/e, ch11.5](https://static.lwn.net/images/pdf/LDD3/ch11.pdf) - [include/linux/list.h](https://github.com/torvalds/linux/blob/master/include/linux/list.h) #### Q2: Linux 應用 linked list 在哪些場合?舉三個案例並附上對應程式碼,需要解說,以及揣摩對應的考量 - Job scheduling Process 的排程是用 priority queue 來實做,而 priority queue 又可以用 linked list 來作 - [merge sort](https://github.com/Lukechin/mergesort_doubly_linked_list/blob/master/mergesort.c) ```c void mergeSort(List** head_ref) { if(!head_ref || !(*head_ref)->next) return; List *left, *right; split(*head_ref, &left, &right); mergeSort(&left); mergeSort(&right); *head_ref = merge(left, right); } ``` - #### Q3: GNU extension 的 [typeof](https://gcc.gnu.org/onlinedocs/gcc/Typeof.html) 有何作用?在程式碼中扮演什麼角色? - [Using the GNU Compiler Collection (GCC): Typeof](https://gcc.gnu.org/onlinedocs/gcc/Typeof.html) typeof 的作用是用來參考( refer to )一個 expression 的資料型態,他的參數可以有兩種: expression 或 type e.g ```c // with an expression typeof (x[0](1)) // with a type typeof (int *) ``` #### Q4: 解釋以下巨集的原理 ```Clike #define container_of(ptr, type, member) \ __extension__({ \ const __typeof__(((type *) 0)->member) *__pmember = (ptr); \ (type *) ((char *) __pmember - offsetof(type, member)); \ }) ``` - container_of() - Calculate address of object that contains address ptTW r #### Q5: 除了你熟悉的 add 和 delete 操作,`list.h` 還定義一系列操作,為什麼呢?這些有什麼益處? - list_replace replace old entry by new one ```c /** * list_replace - replace old entry by new one * @old : the element to be replaced * @new : the new element to insert * * If @old was empty, it will be overwritten. */ static inline void list_replace(struct list_head *old, struct list_head *new) { new->next = old->next; new->next->prev = new; new->prev = old->prev; new->prev->next = new; } ``` - list_move delete from one list and add as another's head ```c /** * list_move - delete from one list and add as another's head * @list: the entry to move * @head: the head that will precede our entry */ static inline void list_move(struct list_head *list, struct list_head *head) { __list_del_entry(list); list_add(list, head); } ``` - list_splice 這個 function 可以將兩個 linked list 合併成一個,可以用來實做 **merge sort** ```c /** * list_splice - join two lists, this is designed for stacks * @list: the new list to add. * @head: the place to add it in the first list. */ static inline void list_splice(const struct list_head *list, struct list_head *head) { if (!list_empty(list)) __list_splice(list, head, head->next); } ``` #### Q6: `LIST_POISONING` 這樣的設計有何意義? #### Q7: linked list 採用環狀是基於哪些考量? - 設計上較單純 #### Q8: `list_for_each_safe` 和 `list_for_each` 的差異在哪?"safe" 在執行時期的影響為何? - 參考: [直播錄影1:23:00](https://youtu.be/0B0GNPv02zg?t=4980) - safe: 針對特定情況,可以符合預期的運作 - 沒有 safe 的版本,在執行刪除時,有可能將 head 節點也刪除了,因此 safe 版本加入了其他考量 ```c= /** * list_for_each_safe - iterate over a list safe against removal of list entry * @pos: the &struct list_head to use as a loop cursor. * @n: another &struct list_head to use as temporary storage * @head: the head for your list. */ #define list_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; pos != (head); \ pos = n, n = pos->next) ``` "safe" 在執行時期會比沒有 safe 的版本多走一次,因為它多了一個變數 n ,使的它會比沒有 safe 的版本多考慮一個節點,避免找不到 head #### Q9: for_each 風格的開發方式對程式開發者的影響為何? * 提示:對照其他程式語言,如 Perl 和 Python - 這個作法讓走訪 linked list 這個動作可以抽象化表示,將實做的部份隱藏起來 #### Q10: 程式註解裡頭大量存在 `@` 符號,這有何意義?你能否應用在後續的程式開發呢? * 提示: 對照看 Doxygen #### Q11: `tests/` 目錄底下的 unit test 的作用為何?就軟體工程來說的精神為何? #### Q12: `tests/` 目錄的 unit test 可如何持續精進和改善呢? ## 修改 lab0 程式碼 - 先將原本的 lab0-c 專案 設為 branch ```c foest@forest-ubuntu:~/lab0-c$ git checkout -b branch1 foest@forest-ubuntu:~/lab0-c$ git push lab_pc branch1 Username for 'https://github.com': type59ty Password for 'https://type59ty@github.com': Total 0 (delta 0), reused 0 (delta 0) remote: remote: Create a pull request for 'branch1' on GitHub by visiting: remote: https://github.com/type59ty/lab0-c/pull/new/branch1 remote: To https://github.com/type59ty/lab0-c * [new branch] branch1 -> branch1 ``` - clone [linux-list](https://github.com/type59ty/linux-list) - 為了實作 doubly linked list,首先需要在 node 結構中多加一條往回指的 link , prev ### queue.h ```c typedef struct ELE { /* Pointer to array holding string. This array needs to be explicitly allocated and freed */ char *value; struct ELE *next; struct ELE *prev; } list_ele_t; ``` ### queue.c - q_insert_head, q_insert_tail 新增一個節點 newh 之後要指定他的 prev 該指向哪裡,考慮兩種情況: 1. q->size == 0 ```c q->tail = newh; newh->next = NULL; newh->prev = NULL; ``` 2. q->size != 0 ```c newh->prev = NULL; newh->next = q->head; q->head->prev = newh; ``` - q_reverse 由於多了一個 prev link,所以在作 reverse 功能時可以用兩個額外的 pointer 就完成,比原本的版本少用一個 pointer ```c //doubly linked list version q_reverse void q_reverse(queue_t *q) { if ((q == NULL) || (q->size <= 1)) return; list_ele_t *current; list_ele_t *temp; current = q->head; q->tail = q->head; while(current != NULL){ temp = current->prev; current->prev = current->next; current->next = temp; current = current->prev; } temp = temp->prev; temp->prev = NULL; q->head = temp; } ``` - 可正常編譯 ```c forest@pop-os:~/lab0-c$ make gcc -O0 -g -Wall -Werror -c queue.c gcc -O0 -g -Wall -Werror -o qtest qtest.c report.c console.c harness.c queue.o ``` ### qtest ```c forest@pop-os:~/lab0-c$ ./qtest cmd>new cmd>new q = [] cmd>ih 1 cmd>ih 1 q = [1] cmd>it 2 cmd>it 2 q = [1 2] cmd>it 3 cmd>it 3 q = [1 2 3] cmd>rh cmd>rh Removed 1 from queue q = [2 3] cmd>reverse cmd>reverse q = [3 2] cmd>reverse cmd>reverse q = [2 3] cmd>free cmd>free q = NULL ```