# 2019q3 Homework3 (list)
contributed by < `uccuser159` >
## 自我檢查清單
* **為何 Linux 採用 macro 來實作 linked list?一般的 function call 有何成本?**
使用 macro 可以在程式被編譯前就先處理定義的函式或數值,例如:
```css=
#define POWER(x) (x)*(x)*(x)
```
* 使用 macro 會占用較多的記憶體,但程式的控制權不用轉移(即不需要 stack 的push 或 pop),所以程式執行速度較快,有儲存空間的成本。
* 使用 function call 會占用較少的記憶體,但程式的控制權需要轉移(即需要 stack 的push 或 pop),所以程式執行速度較慢,有執行時間的成本。
因為 linked list 要用到很多函式來執行一個 task ,所以適合使用 macro 來定義函式來增加執行效率。
* **Linux 應用 linked list 在哪些場合?舉三個案例並附上對應程式碼,需要解說,以及揣摩對應的考量**
* **GNU extension 的 typeof 有何作用?在程式碼中扮演什麼角色**
> [gcc manual](https://gcc.gnu.org/onlinedocs/gcc-9.2.0/gcc.pdf) [6.7] 提到 Another way to **refer to the type of an expression** is with **typeof**
`typeof` 會參考表示式的型態,其宣告方式有兩種:
* With an expression
`typeof (x[0](1))`
假設 x 為陣列透過指標的方式傳入function中,則得到 function 回傳值的 type
* With a type
`typeof (int *)`
得到的 type 是 pointers to int
因為macro本身沒辦法做型態檢查,只是單純的將值帶入並展開,使用 typeof 的話可以讓 macro 在不同 type 的變數皆可以使用,會變得更有彈性。
* **解釋以下巨集的原理**
```cpp
#define container_of(ptr, type, member) \
__extension__({ \
const __typeof__(((type *) 0)->member) *__pmember = (ptr); \
(type *) ((char *) __pmember - offsetof(type, member)); \
})
```
`container_of`可以在 struct 中只知道某一 struct 成員的位址的情況下,抓出此 struct 的起始位址。例如:
```cpp=
/* The student structure definition */
typedef struct student {
char name[16];
int id;
char addr[64];
char phone_num[16];
}STUDENT, *STUDENT_PTR;
```
根據上面的 struct ,若我們知道的是某個 student 的 phone_num,要找出此為 student 和 id ,就可以使用`container_of`:
```cpp=
void print_id (char *phone){
STUDENT_PTR someone = NULL;
someone = container_of(phone,struct student,phone_num);
return;
}
```
在 `print_id` 函式中, phone 即為某學生的電話,而`container_of`做了以下這些事:
`(type *) 0)->member`:
將 0 轉成指向 "student" 這個 struct 型態的 pointer,並將 poniter 指向 phone_num
`__typeof__(((type *) 0)->member)`:
取得 phone_num 的 type
`__typeof__(((type *) 0)->member) *__pmember`:
宣告一個型態為 phone_num 的指標 __pmember
`__typeof__(((type *) 0)->member) *__pmember = (ptr)`:
__pmember 得到 phone 的位址
* [offsetof](https://elixir.bootlin.com/linux/latest/source/include/linux/stddef.h#L15) 定義在 </include/linux/stddef.h> 中,用來計算某一個 struct 的成員相對於該結構起始位址的偏移量( offset )
`offsetof(type, member)`:
計算 struct 開頭到 phone_num 這個成員的偏移量
`(type *) ((char *) __pmember - offsetof(type, member))`
得到 struct 的起始位置,並轉成 "student" 這種 struct 型態
* Reference
-[Linux Kernel: container_of 巨集](http://adrianhuang.blogspot.com/2010/01/linux-kernel-containerof.html)
* **除了你熟悉的 add 和 delete 操作,list.h 還定義一系列操作,為什麼呢?這些有什麼益處?**
list.h 內都是 macro 或是 static inline 的宣告,都可以降低時間複雜度,讓執行效能增加,且可以簡化程式設計,像一個功能塊,例如想要檢查一個 linked list 是否為空的,就可以使用`list_empty`,註解提到 Return: 0 - list is not empty !0 - list is empty
```css=
/**
* list_empty() - Check if list head has no nodes attached
* @head: pointer to the head of the list
*
* Return: 0 - list is not empty !0 - list is empty
*/
static inline int list_empty(const struct list_head *head)
{
return (head->next == head);
}
```
* **LIST_POISONING 這樣的設計有何意義?**
在`list_del()`中:
```cpp=
/**
* list_del() - Remove a list node from the list
* @node: pointer to the node
*
* The node is only removed from the list. Neither the memory of the removed
* node nor the memory of the entry containing the node is free'd. The node
* has to be handled like an uninitialized node. Accessing the next or prev
* pointer of the node is not safe.
*
* Unlinked, initialized nodes are also uninitialized after list_del.
*
* LIST_POISONING can be enabled during build-time to provoke an invalid memory
* access when the memory behind the next/prev pointer is used after a list_del.
* This only works on systems which prohibit access to the predefined memory
* addresses.
*/
static inline void list_del(struct list_head *node)
{
struct list_head *next = node->next;
struct list_head *prev = node->prev;
next->prev = prev;
prev->next = next;
#ifdef LIST_POISONING
node->prev = (struct list_head *) (0x00100100);
node->next = (struct list_head *) (0x00200200);
#endif
}
```
表示當我們在刪除 linked list 的某個 node 時,因為此 node 只是脫離原本的 linked list,記憶體沒有被釋放掉,反而指向`LIST_POISONING`這個位址,用意為防止讓程式設計者在已被移除的 node 再被用來執行 linked list 相關操作。
* **Linked list 採用環狀是基於哪些考量?**
使用環狀 linked list 演算法較一般的 linked list 來的容易,彈性也較大,因為沒有第一個或是最後一個的問題,操作環狀 linked list 並不因為操作位置的不同而需要有不同的解決方法,例如`list_del`並不會因為 node 的位置不同,而有不同的操作。
* **什麼情境會需要對 linked list 做排序呢?舉出真實世界的應用,最好在作業系統核心出現**
* **什麼情境會需要需要找到 第 k 大/小元素 呢?又,你打算如何實作?**
* **list_for_each_safe 和 list_for_each 的差異在哪?“safe” 在執行時期的影響為何?**
這兩個巨集都是走訪 linked list 用的,但是delete 這項操作只能在 list_for_each_safe 中:
```cpp=
/**
* list_for_each_safe - iterate over list nodes and allow deletes
* @node: list_head pointer used as iterator
* @safe: list_head pointer used to store info for next entry in list
* @head: pointer to the head of the list
*
* The current node (iterator) is allowed to be removed from the list. Any
* other modifications to the the list will cause undefined behavior.
*/
#define list_for_each_safe(node, safe, head) \
for (node = (head)->next, safe = node->next; node != (head); \
node = safe, safe = node->next)
```
由於 list_for_each_safe 會預先儲存當前 node 的下一個 node(safe node),所以即使在當回的迴圈中將 node 才 linked list 中移除,也可以繼續走訪到下一個節點(safe node)
* **for_each 風格的開發方式對程式開發者的影響為何?**
for_each 這種風格的開發方式就像目前深度學習工具 [Keras](https://keras.io/getting-started/functional-api-guide/) 一樣,有許多的API 可供程式開發者使用,可以把原本很複雜的東西用成一個 function 變的簡單易懂,從簡單到複雜,都一步步的指引與解釋。
* **tests/ 目錄底下的 unit test 的作用為何?就軟體工程來說的精神為何?**
unit test 的作用即是在測試每個 list.h 所定義的功能是否執行正確,以`list_del.c`為例,即是在測試 list.h 的 `list_del()`:
```cpp=
#include <assert.h>
#include "list.h"
#include "common.h"
int main(void)
{
struct list_head testlist;
struct listitem item;
INIT_LIST_HEAD(&testlist);
assert(list_empty(&testlist));
list_add(&item.list, &testlist);
assert(!list_empty(&testlist));
list_del(&item.list);
assert(list_empty(&testlist));
return 0;
}
```
此精神即是"Divide and conquer",將 list.h 的每個 function 先個別拿出來做測試。
* **tests/ 目錄的 unit test 可如何持續精進和改善呢?**
* 參考資料
[rex662624, e94046165 的共筆](https://hackmd.io/@k3hMyB9FTOyoCTcooh_YFA/SyFUZ6YTz?type=view)