# 2019q1 Homework1 (list)
contributed by < `ChihAnLin0607` >
## 1. 為何 Linux 採用 macro 來實作 linked list?一般的 function call 有何成本?
每一個 function call 之前都需要先將當下的狀態(暫存器) push 進 stack 中保存,然後才能跳至函式內部執行。執行完後也需要再將 stack 中的狀態 pop 出來還原。
而 macro 在編譯階段,就將呼叫 macro 的位置用定義好的敘述展開,所以不需要先 push 狀態,結束後再 pop 狀態。
所以總結來說, macro 作法是用空間換取時間,雖然少了狀態保存與恢復的動作與時間,但是會增加程式碼的長度; function call 則是用時間換取空間,雖然每一次的呼叫都需要狀態切換的時間成本,但是不論呼叫多少次,都是執行記憶體空間內的同一段代碼。
為了驗證以上論點,我做了以下兩支程式:macro.c和func_call.c,他們行為很簡單,就是呼叫 foo() 110000 次,然後 foo() 會將一個全域變數 i 的值增加 1:
```clike
//macro.c
#define foo() i++
int i = 0;
int main()
{
// 一共 22 * 5000 個 foo()
foo();foo():foo();foo():foo();foo();
foo();foo():foo();foo():foo();foo();
foo();foo():foo();foo():foo();foo();
.
.
.
printf("Elapsed Time: %f sec.\n", (double)clock()/CLOCKS_PER_SEC);
return 0;
}
```
func_call.c 和 macro.c 很像只是將 foo 改成函式:
```c
//func_call.c
int i = 0;
void foo()
{
i++;
}
int main()
{
// 一共 22 * 5000 個 foo()
foo();foo():foo();foo():foo();foo();
foo();foo():foo();foo():foo();foo();
foo();foo():foo();foo():foo();foo();
.
.
.
printf("Elapsed Time: %f sec.\n", (double)clock()/CLOCKS_PER_SEC);
return 0;
}
```
clock() 會回傳程序執行開始到被呼叫時,CPU 花在該程序的 clock 數,除上定義於 time.h 中的 CLOCKS_PER_SEC 就可以得到呼叫 clock 之前花了多少時間。
分別將兩者編譯成 macro 和 func_call:
```shell
$ gcc macro.c -o macro
$ gcc func_call -o func_call
```
若上方推論正確,應該可以得到以下結果:
* macro會比func_call佔用更多空間
* macro的執行速度會比func_call快
對兩者分別測試20次的結果如下(單位為秒):
macro:
0.009456、0.009505、0.009262、0.009319、0.009517、0.009465、0.009251、0.009339、0.008269、0.009556、0.010748、0.009442、0.009563、0.009321、0.007704、0.009247、0.009625、0.009290、0.007781、0.004047
func_call:
0.012496、0.018345、0.012943、0.013356、0.012168、0.013422、0.014048、0.012483、0.011679、0.015657、0.015038、0.015581、0.015448、0.014399、0.013235、0.015658、0.007988、0.012845、0.014701、0.016542
雖然其實應該要用統計方法(虛無假設)決定兩者所花時間一不一樣,但我決定先寫其他題目,這邊先用肉眼判斷<u> macro 所花時間明顯比 func_call 少</u>。
至於原因,可以利用objdump看出其中端倪:
```shell
$objdump -D macro > macro.lst
$objdump -D func_call > func_call.lst
```
以下是 macro.lst 的部份內容:
```
00000000004004d6 <main>:
4004d6: 55 push %rbp
4004d7: 48 89 e5 mov %rsp,%rbp
4004da: 8b 05 54 cb 1b 01 mov 0x11bcb54(%rip),%eax # 15bd034 <i>
4004e0: 83 c0 01 add $0x1,%eax
4004e3: 89 05 4b cb 1b 01 mov %eax,0x11bcb4b(%rip) # 15bd034 <i>
4004e9: 8b 05 45 cb 1b 01 mov 0x11bcb45(%rip),%eax # 15bd034 <i>
4004ef: 83 c0 01 add $0x1,%eax
4004f2: 89 05 3c cb 1b 01 mov %eax,0x11bcb3c(%rip) # 15bd034 <i>
4004f8: 8b 05 36 cb 1b 01 mov 0x11bcb36(%rip),%eax # 15bd034 <i>
4004fe: 83 c0 01 add $0x1,%eax
400501: 89 05 2d cb 1b 01 mov %eax,0x11bcb2d(%rip) # 15bd034 <i>
```
func_call.lst 部份內容如下:
```
00000000004004d6 <foo>:
4004d6: 55 push %rbp
4004d7: 48 89 e5 mov %rsp,%rbp
4004da: 8b 05 54 eb c7 00 mov 0xc7eb54(%rip),%eax # 107f034 <i>
4004e0: 83 c0 01 add $0x1,%eax
4004e3: 89 05 4b eb c7 00 mov %eax,0xc7eb4b(%rip) # 107f034 <i>
4004e9: 90 nop
4004ea: 5d pop %rbp
4004eb: c3 retq
00000000004004ec <main>:
4004ec: 55 push %rbp
4004ed: 48 89 e5 mov %rsp,%rbp
4004f0: b8 00 00 00 00 mov $0x0,%eax
4004f5: e8 dc ff ff ff callq 4004d6 <foo>
4004fa: b8 00 00 00 00 mov $0x0,%eax
4004ff: e8 d2 ff ff ff callq 4004d6 <foo>
400504: b8 00 00 00 00 mov $0x0,%eax
400509: e8 c8 ff ff ff callq 4004d6 <foo>
```
由上面兩個 objdump 出的內容可以看出,macro 的 main 函式中,會將 foo() 換成以下三行組合語言代碼:
```
mov 0x11bcb54(%rip),%eax # 15bd034 <i>
add $0x1,%eax
mov %eax,0x11bcb4b(%rip) # 15bd034 <i>
```
簡單來說就是把 i 的值放進 eax 中,然後把 eax 的值增加一,再把新的 eax 值存回 i 中來完成 i++。
而func_call則是把foo()翻成:
```
mov $0x0,%eax
callq 4004d6 <foo>
```
直接跳去 foo 函式所在的記憶體位置,而foo函式內容除了有剛剛 macro.lst 中看到的三行 i++ 編譯而成的組語外,還多包含了五行指令:
```
<foo>:
push %rbp # 這裡
mov %rsp,%rbp # 這裡
#以下三行由 i++ 編譯而來
mov 0xc7eb54(%rip),%eax # 107f034 <i>
add $0x1,%eax
mov %eax,0xc7eb4b(%rip) # 107f034 <i>
nop # 這裡
pop %rbp # 這裡
retq # 這裡
```
而 func_call 這多的五行指令就是造成其執行時間較 macro 的原因。
而觀察objdump輸出的同時也能看出,空間的佔用大小, macro 將每一個 foo() 都編譯程三行組語代碼; func_call 將每個 foo() 都編譯兩行組語代碼,外加一個八行組語代碼的空間存放 foo 函式本身。結果就是每一次的 foo() 呼叫增加 macro 和 func_call 的代碼大小差異,<u>呼叫愈多次 foo(),macro 會比func_call 大得愈多</u>。
以上的結果證實了一開始的推論是正確的。
:::warning
編譯器最佳化可將 `static inline` 的函式做適度地展開,可對照實驗。另外,像是 gcc 和 clang/llvm 這樣現代的編譯器設計中,有 [LTO (link time optimization)](https://eklitzke.org/how-gcc-lto-works),對上述 function inling 有更好的表現
:notes: jserv
:::
參考來源:自己
## 2. Linux 應用 linked list 在哪些場合?舉三個案例並附上對應程式碼,需要解說,以及揣摩對應的考量
## 3. GNU extension 的 typeof 有何作用?在程式碼中扮演什麼角色?
### typeof 的作用
typeof 有三種樣貌,分別為 typeof 、 \_\_typeof__ 、\_\_typof。解釋他們區別之前要先知道 GNU C 和標準 C 的差別:
* 標準 C:簡單來說就是被某個組織定義出來的C, ANSI C ,ISO C, Standard C C89 C99 C11這些其實都是標準C,只是不同時間定義出來的不同版本。
* GNU C:GNU以標準C為基礎,擴增其他功能實作出來的C。而typeof就是GNU C做的其中一個擴充。
所以如果要標準C的編譯器去認得這些擴充,就必須要在這些擴充的功能名稱前面加上兩個底線「\_\_ 」, \_\_typeof__ 和 \_\_typeof 就是這樣來的。所以三者的功能其實一樣,就是編譯器會在編譯階段將 typeof(n) (或其他兩者)換成 n 的資料型態,也就是說若 n 為一 char 的變數名稱,那 typeof(n) 就會被換成 char。
下方有個測試能看出實際狀況:
```clike
// test.c
#include<stdio.h>
int main()
{
int a;
__typeof__(int) b;
&a == &b;
__typeof(int) c;
&a == &c;
typeof(int) d;
&a == &d;
return 0;
}
```
會將兩變數的位址做比較是因為若兩者的類型不一樣,就會丟出 `warning: comparison of distinct pointer types lacks a cast
`的信息 ,所以如果沒有丟出,就表示typeof有照著預期的行為走。
現在將他編譯,因為我的GCC版本為5.4.0,所以若不設定參數,預設是使用 gnu11 的標準:
```shell
$gcc test.c
$
```
結果並沒有跳出任何訊息,表示編譯器有順利地將 typeof(int) 換成 int 。現在試試看用 C99 標準的 C 編譯看看:
```shell=
test.c: In function ‘main’:
test.c:14:5: warning: implicit declaration of function ‘typeof’ [-Wimplicit-function-declaration]
typeof(int) d;
^
test.c:14:12: error: expected expression before ‘int’
typeof(int) d;
^
test.c:14:17: error: expected ‘;’ before ‘d’
typeof(int) d;
^
test.c:15:12: error: ‘d’ undeclared (first use in this function)
&a == &d;
^
test.c:15:12: note: each undeclared identifier is reported only once for each function it appears in
```
#2果然跳出 warning 訊息,警告說 typeof 沒有被定義。
### typeof 在程式碼中扮演的角色
typeof 被大量運用在 macro 之中,因為 macro 本身並不像函式一樣,能限制參數的資料型態,它只是很單純的做程式碼的代換或展開,但如果在 macro 中使用 typeof 宣告出和參數擁有一樣型態的變數,然後再用利用上述提到的不同指標間比較會丟出 warning的技巧,就可以在 macro 的參數不符合期待時,丟出 warning信號。以下是 Linux kernel 中運用的例子:
```clike
//linux/include/linux/overflow.h
/*
* For simplicity and code hygiene, the fallback code below insists on
* a, b and *d having the same type (similar to the min() and max()
* macros), whereas gcc's type-generic overflow checkers accept
* different types. Hence we don't just make check_add_overflow an
* alias for __builtin_add_overflow, but add type checks similar to
* below.
*/
#define check_add_overflow(a, b, d) ({ \
typeof(a) __a = (a); \
typeof(b) __b = (b); \
typeof(d) __d = (d); \
(void) (&__a == &__b); \
(void) (&__a == __d); \
__builtin_add_overflow(__a, __b, __d); \
})
#define check_sub_overflow(a, b, d) ({ \
typeof(a) __a = (a); \
typeof(b) __b = (b); \
typeof(d) __d = (d); \
(void) (&__a == &__b); \
(void) (&__a == __d); \
__builtin_sub_overflow(__a, __b, __d); \
})
```
註解部份也有提到,在做 overflow 的檢查前,他們會先確定 a 、 b 、 \*d的 type 一樣。為了達到這目的,會先利用 typeof 宣告類型和 a 一樣的 __a 、 類型和 b 一樣的 __b 、類型和 d 一樣的 __d ,並拿 &__a 和 &__b 比較、 &__a 和 __d,若 a 、 b 和 *d 類型一樣,那就不會丟出 warning 訊息。
參考來源:
1. [Referring to a Type with typeof
](https://gcc.gnu.org/onlinedocs/gcc-3.3/gcc/Typeof.html)
2. [\_\_typeof__() 、 __typeof() 、 typeof()的區別](https://my.oschina.net/u/2472425/blog/1083157)
## 4. 解釋巨集原理
#define container_of(ptr, type, member) \\
\_\_extension__({ \\
const \_\_typeof\_\_(((type *) 0)->member) *__pmember = (ptr); \\
(type *) ((char *) __pmember - offsetof(type, member)); \\
})
這巨集分為以下兩個步驟:
__1. const \_\_typeof\_\_(((type *) 0)->member) *\_\_pmember = (ptr);__
這裡宣告一個指標 __pmember ,其型態和 type 的 member 成員一樣,然後將 ptr 的值指定給 __pmemeber。
從這裡就可以得到幾個訊息:
* type 的型態必須是 struct ,否則會丟出類似以下錯誤:
`error: request for member ‘$(member)’ in something not a structure or union`
* member 必須是 type 的成員。若不是的話會丟出類似以下錯誤:
`error: ‘$(type)’ has no member named ‘$(member)’`
* ptr 的類型要跟 type 的 member 成員的類型一樣,否則會出現類似以下警告:
`warning: initialization from incompatible pointer type`
__2. (type *) ((char *) \_\_pmember - offsetof(type, member));__
先來看看 offsetof 的作用,以下為 wiki 的解釋:
`It evaluates to the offset (in bytes) of a given member within a struct or union type, an expression of type size_t.`
也就是說 offsetof(type, member) 會得到 member 在 type 中的偏移量。另外因為有上述第一步驟,所以可以確定 member 一定是 type 的成員。
所以 contain_of 第二步驟就是拿這個「值和 ptr 一樣」的 __pmember 去扣掉 member 在 type 中的相對位置,<u>結果會得到 ptr 所在的 struct 的記憶體起始位置</u>。以下為舉例和圖解。
現在有一個 struct 定義與宣告如下:
```clike
struct Node {
int data;
struct list_head list;
} node;
```
另外我透過別的 list_head 的 next 指標知道 node 底下的 list 記憶體位置(假定其為 node_list , 型態為 struct list_head* )。 node 的記憶體配置示意圖如下(64位元系統):

這時若我想用 node_list 反推 node 的記憶體位置,就可以使用 contain_of(node_list, struct Node, list):
首先第一步驟會宣告一個 __pmember,方法如下:
`const __typeof__(((struct Node*)0)->list) __pmember = node_list;`
__pmember 其實就是 node_list的分身,他們有一樣的型態、指向一樣的記憶體位置。
第二步驟則是:
`(struct Node *) ((char *) __pmember - offsetof(struct Node*, list));`
由上方的 node 記憶體配置圖可以知道, list 在 node 中的偏移量為 8 ,也就是說 offsetof(struct Node*, list) 等於 8 。而 __pmember 指向的是 X+8 ,扣掉偏移量 8 後,就會得到 node 的記憶體起始位置。
## 5. 除了你熟悉的 add 和 delete 操作,list.h 還定義一系列操作,為什麼呢?這些有什麼益處?
list.h 定義許多 macro 來打包許多特定行為,像是 list_for_each 用來走遍 list 內所有資料、 list_first_entry 用來取得 list 的第一筆資料等等。這些打包起來的 macro 能讓許多行相通目的的程式碼變成單行 macro ,提供開發人員更直觀、更高層次的方式來維護與開發。除此之外,也較能減少錯誤的出現。
## 6. LIST_POISONING 這樣的設計有何意義?
在 list.h 中若 LIST_POISONING 啟動,被刪除的 struct list_head 實體的 next 和 prev 就會指向一個 kernel 無法存取的記憶體位置。在這個指定的過程中是沒有問題的,不論是編譯階段或是執行階段都不會有錯誤出現,但是當有人試著對這段記憶體做操作時,就會出現記憶體區段錯誤的信息。見以下範例:
```clike
// poison_test.c
#include<stdio.h>
#define POISON 0x00100100
int main()
{
int* a = (int *) (0x00100100);
printf("Next step will do '*a = 0'\n");
*a = 10;
return 0;
}
~
```
編譯與執行結果:
```shell
$gcc poison_test.c
$./a.out
Next step will do '*a = 0'
程式記憶體區段錯誤 (core dumped)
```
將被從 list 中移除的 list_node實體的 next 和 prev 指向這麼一個非法的記憶體位置,就能夠確保他們不會被使用,一旦真的被使用,就會出現錯誤。
## 7. linked list 採用環狀是基於哪些考量?
## 8. 什麼情境會需要對 linked list 做排序呢?舉出真實世界的應用,最好在作業系統核心出現
## 9. 什麼情境會需要需要找到 第 k 大/小元素 呢?又,你打算如何實作?
## 10. list_for_each_safe 和 list_for_each 的差異在哪?“safe” 在執行時期的影響為何?
```clike=
/**
* list_for_each - iterate over list nodes
* @node: list_head pointer used as iterator
* @head: pointer to the head of the list
*
* The nodes and the head of the list must must be kept unmodified while
* iterating through it. Any modifications to the the list will cause undefined
* behavior.
*/
#define list_for_each(node, head) \
for (node = (head)->next; node != (head); node = node->next)
/**
* 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,這樣做的目的其實註解裡也有提到,這樣能夠允許當下的 node 被刪除,因為下一個已經被 safe 記下,不會因為當下的 node 被移除,而遺失指向下一個 node 的指標。所以 safe 能夠降低執行時期發生 Memory leak 的可能。
## 11. for_each 風格的開發方式對程式開發者的影響為何?
提示:對照其他程式語言,如 Perl 和 Python
## 12. 程式註解裡頭大量存在 @ 符號,這有何意義?你能否應用在後續的程式開發呢? (提示: 對照看 Doxygen)
@ 是個讓 Doxygen 抓取的標籤特徵。只要熟悉 Doxygen 的分析規則,在後續的程式開發中都依照其規則撰寫註解,這樣就能隨時利用 Doxygen 來快速地產生程式說明檔。
Doxygen 是一個開發輔助工具,他能就將程式內部特定格式的註解轉成 html 等特定檔案格式,自動生成程式碼的說明文件。而 @ 就是剛剛所提到的特定格式。
舉例來說,若 doxygen 讀到以下格式的註解:
```clike
/** @mainpage
This is the mainpage.
*/
```
那 doxygen 就會在輸出文件中建立一頁 mainpage ,內文為「 This is the mainpage. 」。

而這所謂的特定註解格式其實就是用 __/**__ 和 __*/__ 包含在內的註解。然後在這樣格式的註解內容就可以使用許多 doxygen 預設的標籤,用法跟 html 的標籤有點像,只是名字不一樣,也不需要結束標籤。所以回到剛剛 mainpage 的例子,這例子的標籤就是 @mainpage ,當 doxygen 讀到這個標籤,他就會建立一個 mainpage ,然後把同註解內後面全部的內容都放入其中。而那個 @ 就是 doxygen 的標籤特徵,另外除了 @ 外, \ 也能達到一樣的效果,也就是說 @mainpage 和 \mainpage是一樣的。
現在來看一個比較完整的例子:
```clike=
// doxygen_test.c
/** @file
Here is the Detail Description.
*/
#include<stdio.h>
/** @mainpage
This is a main page.
Here is a blod @b word.
*/
int global; /** This is a global var. */
/** Here should be the description of foo()
@param i This number will increase by 1.
*/
void foo(int* i)
{
(*i)++;
}
/** Here should be the description of main()
*/
int main()
{
int i = 0;
foo(&i);
printf("i = %d\n", i);
return 0;
}
```
針對上面範例做一點說明:
@file :標示該文件給 doxygen 標示,如果文件開頭沒有 @file 標籤,那就算該文件在 doxygen 的輸入資料夾內, doxygen也會選擇忽略。
@b :能讓此標籤後面的一個單字在輸出說明檔中變成粗體字。
@parm:函式或變數上方的註解會被 doxygen 認為是在解釋該函式或變數,而 @parm 後方的單字會被認為是該函式的參數,單字後方的一句話則會被當成是該參數的說明。
現在要來用 doxygen 產生上方 doxygen_test.c 的說明文件:
```shell=
$ls
doxygen_test.c
$doxygen -g
$ls
Doxyfile doxygen_test.c
$doxygen Doxyfile
Doxyfile doxygen_test.c html latex
$gnome-open html/index.html
```
#3 會在工作目錄下產生 Doxyfile 的預設設定檔 Doxyfile ,內有許多設定可以修改,但這裡示範就直接使用預設值,而預設值就是分析工作目錄內所有的程式碼,並將結果輸出到工作目錄下。
#5 指令是使用叫 doxygen 用 Doxyfile 內的設定值開始分析註解並且產出說明檔,共會產出兩個資料夾 html 和 latex。
#8 點選 html 內的 index.html 就能用瀏覽器來看 doxygen_test.c 的說明檔了。
結果如下:


參考來源:
1. [Doxygen - All Commands簡易說明(未完成) | 給姆喇低賽](https://wingedrobin.blogspot.com/2015/03/doxygen-all-commands.html)
2. [21世紀C語言之27: doxygen](https://ithelp.ithome.com.tw/articles/10161530)
## 13. tests/ 目錄底下的 unit test 的作用為何?就軟體工程來說的精神為何?
## 14.tests/ 目錄的 unit test 可如何持續精進和改善呢?
## Merge Sort
程式碼:[merge_sort.c](https://github.com/ChihAnLin0607/linux-list/blob/master/merge_sort/merge_sort.c)
思路:
1. 讀取cities.txt內的九萬多筆資料,並用此建立 testlist 。
2. 同時用陣列 values 存取一樣內容、一樣順序的資料。
3. 利用 merge sort 演算法排序 testlist 內的資料。
4. 使用 qsort 排序 values 內的資料。
5. 依順序一一比較兩者內資料順序是否一致。是的話表示 merge sort 成功。
空間利用:
* 為了節省空間,所以一筆資料只建立一個記憶體實體,不論是 testlist 中的 item 或是 values 都只是紀錄該筆資料的記憶體位置而已。
* 這樣的好處除了節省空間外,在最後一步驟 testlist 和 values 內資料比較時,也可以直接比較其指標指向的記憶體位置是否一樣就好,而不用真的比較其字串內容。
時間:
將 Dict 內的測試當成輸入,比較資料由少到高的所需時間變化:

可以看出雖然資料數量由一增加到九萬多,但所花時間只增加到 0.045 秒左右而已。符合時間複雜度 $O(nlgn)$ 的結果。