# 2018q1 第 9 週測驗題
###### tags:`mrwenwei`,`shaimejump520`
contributed by <`mrwenwei`>, <`shaimejump520`>
* [第九週測驗題](https://hackmd.io/FJ1WAslLSIahP7diUKhG1A?view)
### 第1題
原程式碼:
```clike=
#include <math.h>
#include <stdio.h>
double powDoubleFloat(double base, float power) {
return pow(base, power);
}
int main() {
double (*powFloatDouble)(float, double) =
(double (*)(float, double)) & *** & powDoubleFloat;
printf("(0.99)^100: %lf \n", powDoubleFloat(0.99, 100));
printf("(100)^0.99: %lf \n", powFloatDouble(100, 0.99));
printf("(0.99)^100: %lf \n", powFloatDouble(0.99, 100));
return 0;
}
```
:::success
延伸問題:
1. 依據 IEEE 754 規範,解釋為何輸出如此;
2. 程式碼第 7 和第 8 行的 `& *** &` 做了什麼事?請參閱 C99/C11 規範並解釋;
3. 參考 [Fast Exponentiation Algorithms](http://homepages.math.uic.edu/~leon/cs-mcs401-s08/handouts/fastexp.pdf),實作 [Fixed-point arithmetic](https://en.wikipedia.org/wiki/Fixed-point_arithmetic) 版本的 `pow` 函式並驗證;
:::
* 用 GDB 進行分析( test.c 內包含上述程式碼 )
```
gcc test.c -o test -lm -g
gdb -tui test
```
注意!若要使用 GDB ,在編譯時要加入 `-g` 參數。
GDB 時加入 `-tui` 參數,可以在 debug 時顯示 source code
:::info
可嘗試使用 [cgdb](https://cgdb.github.io/),有更好的顯示
:notes: jserv
:::
將 breakpoint 設在第3行,可以方便觀察 function parameter 的傳入值:
```
powDoubleFloat (base=5.5355285709140466e-315, power=5.84860315e+35)
```
發現在第 11 行的 function call,其參數的傳入值,已與我們預想的 base = 100.0, power = 0.99 有很大的出入
base $= 5.5355285709140466\ \times10^{-315}$ 為非常小的數字,趨近於 0 所以再取 0 的 power($5.84860315\times10^{35}$)次,也會是 0
#### 那為何參數的傳入值會與我們預想的差這麼多呢?
* 先回想,function 的呼叫,首先會將 return address 置入 stack 中,再**將 parameter 依序也放入 stack 中**,真正進入到 function 的執行片斷時,會先**再反序將參數從 stack 中取出**,問題便出在這裡了!
:::warning
這說法不精確!請用 [System V Application Binary InterfaceAMD64 Architecture Processor Supplement](https://software.intel.com/sites/default/files/article/402129/mpx-linux64-abi.pdf) 來解釋,網路上有很多討論仍以「直覺」猜測,卻不是參照第一手材料和搭配實驗來分析。
:notes: jserv
:::
* `powFloatDouble(100, 0.99)`,我們先將 值為 100 的 float(**32-bits**) 與值為 0.99 的 double(**64-bits**) 放入 stack 中,而後先取出一個 float(**32-bits**) 再取出一個 double(**64-bits**),按照 stack 的性質,我們已經取出了與我們設想不同的數值進行運算了
* 用敘述的太沒有畫面,來讓 GDB 講給我們聽吧:
> 結果詳閱了上方老師給的文件後,根據內述的函式參數傳遞方式實際用 GDB 操作後,發現與設想的不太相同,目前尚未找到原因來解釋,將繼續實驗嘗試 [name=shaimejump520]
#### `& *** &` 之謎:
* C99 [6.5.3.2] **Address and indirection operators**
* &*E is equivalent to E (even if E is a null pointer), and &(E1[E2]) to ((E1)+(E2)).
* It is always true that if E is a function designator or an lvalue that is a valid operand of the unary & operator, *&E is a function designator or an lvalue equal to E. If *P is an lvalue and T is the name of an object pointer type, *(T)P is an lvalue that has a type compatible with that to which T points.
* 經由上述,我們可以將程式中第 8 行中的 `& *** &` 視為 `*`,綜合來說, 7 8 行的宣告為:宣告一個 pointer of function `powFloatDouble` 其 return type 為 double 而參數為 float 及 double ,而該指標指向一個已被隱含轉型的 function `powDoubleFloat`
* 其實規格書中提到的最耐人尋味的,也就是上面第一句 `&*E is equivalent to E (even if E is a null pointer)`,講到即使 E 是 null pointer,亦符合等價的規則!
* 我們知道,若是我們對 null pointer 取值會產生錯誤,然而若是對 null pointer 取值再取地址,卻能夠正確的回傳
* 對此我們做了個小實驗:
```clike=
#include <stdio.h>
int main(){
int *ptr = 0;
printf("%p", &*ptr); //(nil)
fflush(stdout);
int a = *ptr; //segmentation fault
printf("%p", &a);
return 0;
}
```
* 對此的解釋為,compiler 在編譯時會自己將 `&*ptr` 等價 `ptr` ,並不會實際執行先取值再對值取地址的動作,所以在第 3 行的地放可以正確執行,而在第 10 行就真正執行的對 null pointer 取值的動作,所以導致程式執行出錯。
如此的設計也是符合常理,程式執行時不至於虛耗在那些並無意義的取值取址中。
---
### 測驗 `2`
考慮到以下採用 [Functional Programming](https://en.wikipedia.org/wiki/Functional_programming) 風格開發的 singly-linked list 程式:
```Clike
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
/* integer linked list type */
typedef struct __int_list const *const int_list_t;
static int_list_t const Nil = NULL; /* empty list */
/* singly linked list element */
typedef struct __int_list {
int_list_t next;
int32_t const val;
} node_t;
/* A pointer to where we are about to store the result of computation */
typedef void *const CPS_Result;
/* prototypes for the continuations */
typedef void (*MakeListCallback)(int_list_t list, CPS_Result res);
void make_list(uint32_t const arr_size,
int32_t const array[],
int_list_t lst,
MakeListCallback const cb,
CPS_Result res);
typedef void (*ReversedListCallback)(int_list_t rlist, CPS_Result res);
void reverse(int_list_t list,
int_list_t rlist,
ReversedListCallback const cb,
CPS_Result res);
typedef void (*VoidMappable)(int32_t const val);
void void_map_array(VoidMappable const cb,
uint32_t const size,
int32_t const *const arr);
void list2array(int_list_t list, CPS_Result res);
/* reverse a list and then store it in an array */
void reverse_toarray(int_list_t list, CPS_Result res) {
reverse(list, Nil, list2array, res);
}
static void print_val(int32_t const val) { printf("%d ", val); }
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
int main(int argc, char *argv[]) {
int32_t arr[] = {99, 95, 90, 85, 80, 20, 75, 15, 10, 5};
uint32_t const arr_size = ARRAY_SIZE(arr);
int32_t res[arr_size];
/* call make_list and pass reverse_toarray as the "continuation" */
make_list(arr_size, arr, Nil, reverse_toarray, res);
/* print out the reversed array */
void_map_array(print_val, arr_size, res);
printf("\n");
return 0;
}
/* construct a linked list from an array */
void make_list(uint32_t const arr_size,
int32_t const arr[],
int_list_t lst,
MakeListCallback const cb,
CPS_Result res) {
if (!arr_size) {
cb(lst, res);
return;
}
make_list(arr_size - 1, arr,
&(node_t){.val = arr[arr_size - 1], .next = lst}, cb, res);
}
/* transform a linked list into an array */
void list2array(int_list_t list, CPS_Result res) {
if (Nil == list)
return;
int32_t *array = res;
array[0] = list->val;
list2array(list->next, array + 1);
}
void reverse(int_list_t list,
int_list_t rlist, /* reversed list */
ReversedListCallback const cb,
CPS_Result res) {
if (Nil == list) {
cb(rlist, res);
return;
}
reverse(list->next, &(node_t) K1, cb, res);
}
/* iterate over an array and performs action cb on each element */
void void_map_array(VoidMappable const cb,
uint32_t const size,
int32_t const *const arr) {
if (!size)
return;
cb(arr[0]);
void_map_array(cb, K2, K3);
}
```
程式輸出為 `5 10 15 75 20 80 85 90 95 99`,也就是將輸入的數值順序予以反轉,過程中不需要 malloc/free,請補完上述程式碼。
K1 = ?
* `(a)` {.val = list->val, .next = list}
* `(b)` {.val = list->val, .next = rlist}
* `(c)` {.val = rlist->val, .next = list}
* `(d)` {.val = rlist->val, .next = rlist}
K2 = ?
* `(a)` size - 1
* `(b)` size
* `(c)` size + 1
K3 = ?
* `(a)` arr - 1
* `(b)` arr
* `(c)` arr + 1
:::success
延伸題目:
1. 解釋上述程式碼的運作,並且透過修改和實驗,理解 functional programming (FP) 風格;
2. 比照 FP,改寫上述程式碼,實作出 quick sort,同樣不得配置 heap memory (也就是不得用 malloc/free);
3. 測試這樣的 integer list 和 non-intrusive linked list 的效能差異;
:::
#### 1. 解釋上述程式碼的運作,並且透過修改和實驗,理解 functional programming (FP) 風格
首先必須先瞭解一下程式運作的邏輯,先看一下 reverse() 這個 function。他會將傳進去的 singly linked list 反轉後將結果轉成 array 存在 main() 中先宣告好的 res 中。而他的過程是透過巧妙的遞迴實作完成,下面以圖示說明:
* 第一次呼叫時將原始的 list 傳進去,紅色部分為指標處。
### list:
```graphviz
digraph foo {
rankdir=LR;
node [shape=record];
a [label="{ <data> 99 | <ref> }", width=1.2, color=red]
b [label="{ <data> 95 | <ref> }"];
c [label="{ <data> 90 | <ref> }"];
d [label="..." shape=box];
e [label="{ <data> 5 | <ref> }",width=1.2];
a:ref:c -> b:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false, arrowsize=1.2];
b:ref:c -> c:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
c:ref:c -> d [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
d->e
}
```
* 進入遞迴第一次時將 list->next 放在第一個參數傳了進去,而比較特別的地方是的第二個參數的部分直接將一個 node_t 型態的 linked list 初始化為 val = 目前紅色指標處的 value,next = NULL (rlist 一開始為 NULL),也就是下一層遞迴裡的 rlist。
### list:
```graphviz
digraph foo {
rankdir=LR;
node [shape=record];
a [label="{ <data> 95 | <ref> }", width=1.2, color=red]
b [label="{ <data> 90 | <ref> }"];
c [label="{ <data> 85 | <ref> }"];
d [label="..." shape=box];
e [label="{ <data> 5 | <ref> }",width=1.2];
a:ref:c -> b:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false, arrowsize=1.2];
b:ref:c -> c:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
c:ref:c -> d [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
d->e
}
```
### rlist:
```graphviz
digraph foo {
rankdir=LR;
node [shape=record];
a [label="{ <data> 99 | <ref> }", width=1.2, color=red]
b [label="{ NULL }"];
a:ref:c -> b:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false, arrowsize=1.2];
}
```
* 進第二層遞迴時又將 list->next 傳了進去,第二項參數的 rlist 的 val 初始化為 list->val ,也就是 95,next 再接回去原來傳進來的 rlist,就會變成下圖:
### list:
```graphviz
digraph foo {
rankdir=LR;
node [shape=record];
a [label="{ <data> 90 | <ref> }", width=1.2, color=red]
b [label="{ <data> 85 | <ref> }"];
c [label="{ <data> 80 | <ref> }"];
d [label="..." shape=box];
e [label="{ <data> 5 | <ref> }",width=1.2];
a:ref:c -> b:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false, arrowsize=1.2];
b:ref:c -> c:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
c:ref:c -> d [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
d->e
}
```
### rlist:
```graphviz
digraph foo {
rankdir=LR;
node [shape=record];
a [label="{ <data> 95 | <ref> }", width=1.2, color=red]
b [label="{ <data> 99 | <ref> }"];
c [label="{ NULL }"];
a:ref:c -> b:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false, arrowsize=1.2];
b:ref:c -> c:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false, arrowsize=1.2];
}
```
* 一直到最後 rlist 就會是 list 的反轉了:
### rlist:
```graphviz
digraph foo {
rankdir=LR;
node [shape=record];
a [label="{ <data> 5 | <ref> }", width=1.2, color=red]
b [label="{ <data> 10 | <ref> }"];
c [label="..." shape=box];
d [label="{ <data> 99 | <ref> }"];
e [label="{ NULL }"];
a:ref:c -> b:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false, arrowsize=1.2];
b:ref:c -> c:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false, arrowsize=1.2];
c:ref:c -> d:data [arrowsize=1.2];
d:ref:c -> e [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false, arrowsize=1.2];
}
```
* 終止條件成立時會呼叫一開始傳進去的 list2array() 並將 rlist 轉成 array 存在 res 中,完成 function。
* 故第一題的 K1 就是 ‘{.val = list->val, .next = rlist}’ , 而 void_map_array 是將 array 內的 data 印出來,K2 為遞迴終止條件的變數,故應為 size-1,K3 等同於 arr[i++] 的功能,因此為 arr+1。
* 一開始在看這份 code 時覺得很不習慣,所有的事情都從遞迴以及傳遞參數之間完成,每個功能都拆開來撰寫,上網搜尋了一下 [Functional Programming](https://en.wikipedia.org/wiki/Functional_programming) 各類相關資料,發現大致上有幾個特點:
* 一個運算用的 function 大多只包含條件式以及遞迴呼叫。
* [higher-order functions](https://en.wikipedia.org/wiki/Higher-order_function) 主要有兩個特點:
* function 可當作參數傳入 function 之中。
* function 可以 return function.
* [lazy evaluation](https://en.wikipedia.org/wiki/Lazy_evaluation) :當變數被定義時不會立即 evaluate,而是等待要使用時才 evaluate 並儲存已計算的結果,所以不會重複 evaluate 同一個定義。
* 不支援 flow Controls,像是迴圈以及 If-Else 條件判斷式,只使用 function 及 function 呼叫。
[Side Effect](https://en.wikipedia.org/wiki/Side_effect_(computer_science))
:::warning
關於 FP,可對照看之前報告 [MapReduce with POSIX Thread](https://hackmd.io/s/Hkb-lXkyg),注意裡頭引用的論文
:notes: jserv
:::
#### 2. 比照 FP,改寫上述程式碼,實作出 quick sort,同樣不得配置 heap memory (也就是不得用 malloc/free)
回憶一下 [quicksort](http://notepad.yehyeh.net/Content/Algorithm/Sort/Quick/Quick.php) 演算法的流程:
* 選定數列中的一個值當作 pivot ,而選擇的方法可以是隨機選,也可以是挑出陣列的第一項,當然也可以使用 [median of medians](https://en.wikipedia.org/wiki/Median_of_medians) 確保每次選的 pivot 都是接近中位數的數,以降低 quicksort 遞迴次數。
* 從數列的頭到尾對每個數比較,若大於 pivot 則將其置於右方,剩下的則置於 pivot 左方。
* 對左右兩邊的數列分別都再做上述步驟(也就是遞迴),一直到數列個數為 0 或 1 時結束遞迴。
開始實作演算法的部份,由於 functional progamming 的程式碼有一個很大的特色是:當一個變數建立出來後就不可以在更動他的值,在 C語言中也就是 const 型態的變數,這樣的限制也讓程式碼寫起來相當的不習慣。
我們先嘗試寫了一個 partition 的function:
```clike=
__list_strct partition( int_list_t cur,
int_list_t llist,
int_list_t pivot,
int_list_t rlist) {
if(Nil == cur) {
__list_strct r = {.left = llist, .right = rlist};
return r;
}
/* Put the numbers which are larger than pivot into right. */
if(cur->val > pivot->val) {
return partition(cur->next, llist, pivot,
&(node_t){.val = cur->val, .next = rlist});
}
return partition(cur->next, &(node_t){.val = cur->val, .next = llist}, pivot, rlist);
}
```
回傳值的型態是一個包含兩個 list 指標的 struct,目的是要呼叫 partition 這個 function 後可以拿到兩個分完堆的 list。
```clike=
typedef struct list_strct {
int_list_t left;
int_list_t right;
} __list_strct;
```
然而這樣的作法出現了問題,儘管我們在 partition() 內測試了 function 內容都確定沒寫錯,但當 function return 後做下一步的執行就會出現難以預料的結果或是 segmentation fault,連多一個 printf 都會讓結果截然不同,這樣的結果讓我們困惑,因此我們決定使用睡眠 debug 法則,就這樣睡掉了幾天之後才發現我們犯了一個錯誤。
先觀察一下這行程式碼:
```clike=
/* integer linked list type */
typedef struct __int_list const *const int_list_t;
```
他的意思是定義了一個 int_list_t 的宣告方式,他可以宣告一個本身是 const 的指標並且指向一個 const 的變數。
也就是說如果我們將他當作一個 function 的宣告,則這個 function 就可以被視為是一個 const 的變數,也就是回傳的結果必須固定不變,然而如果每次迴傳都式不同的變數時就會發生錯誤。這裡也讓我們想起 functional programming 的初衷,因此繼續修改程式碼。