# 2018q1 Homework3 (c-review) contributed by <`bauuuu1021`> * [作業要求](https://hackmd.io/s/ByDzIZzjf) ## 第四周 * [題目](https://hackmd.io/s/SyK-WApKM) ### 測驗 1 ![](https://i.imgur.com/iqFZJkY.png) 分析以下程式碼,推敲 `FuncA`, `FuncB`, `FuncC` 的作用,並且推測程式執行結果。 假設條件: * `malloc` 總是成功而且返回的記憶體空間可讀寫 ```Clike #include <stdlib.h> #include <stdio.h> struct node { int data; struct node *next, *prev; }; void FuncA(struct node **start, int value) { if (!*start) { struct node *new_node = malloc(sizeof(struct node)); new_node->data = value; new_node->next = new_node->prev = new_node; *start = new_node; return; } struct node *last = (*start)->prev; struct node *new_node = malloc(sizeof(struct node)); new_node->data = value; new_node->next = *start; (*start)->prev = new_node; new_node->prev = last; last->next = new_node; } void FuncB(struct node **start, int value) { struct node *last = (*start)->prev; struct node *new_node = malloc(sizeof(struct node)); new_node->data = value; new_node->next = *start; new_node->prev = last; last->next = (*start)->prev = new_node; *start = new_node; } void FuncC(struct node **start, int value1, int value2) { struct node *new_node = malloc(sizeof(struct node)); new_node->data = value1; struct node *temp = *start; while (temp->data != value2) temp = temp->next; struct node *next = temp->next; temp->next = new_node; new_node->prev = temp; new_node->next = next; next->prev = new_node; } void display(struct node *start) { struct node *temp = start; printf("Traversal in forward direction \n"); for (; temp->next != start; temp = temp->next) printf("%d ", temp->data); printf("%d ", temp->data); printf("\nTraversal in reverse direction \n"); struct node *last = start->prev; for (temp = last; temp->prev != last; temp = temp->prev) printf("%d ", temp->data); printf("%d ", temp->data); printf("\n"); } int main() { struct node *start = NULL; FuncA(&start, 51); FuncB(&start, 48); FuncA(&start, 72); FuncA(&start, 86); FuncC(&start, 63, 51); display(start); return 0; } ``` ==作答區== `FuncA` 的作用是 * `(a)` 偵測輸入是否為 circular linked list,若是則將現有所有節點內容排序,否則不做事 * `(b)` 建立兩個節點並且安插在結尾,內容都是 `value` * `(c)` 尋找所有節點,當遇到符合給定數值 `value` 的節點時,將 circular linked list 的開頭和剛找到的節點串接 * `(d)` 建立新節點,內容是 `value`,並安插在開頭 * `(e)` 建立新節點,內容是 `value`,並安插在結尾 * `(f)` 建立兩個節點並且安插在開頭,內容都是 `value` * `(g)` `FuncB` 的作用是 * `(a)` 偵測輸入是否為 circular linked list,若是則將現有所有節點內容排序,否則不做事 * `(b)` 建立兩個節點並且安插在結尾,內容都是 `value` * `(c)` 尋找所有節點,當遇到符合給定數值 `value` 的節點時,將 circular linked list 的開頭和剛找到的節點串接 * `(d)` 建立新節點,內容是 `value`,並安插在開頭 * `(e)` 建立新節點,內容是 `value`,並安插在結尾 * `(f)` 建立兩個節點並且安插在開頭,內容都是 `value` `FuncC` 的作用是 * `(a)` 偵測輸入是否為 circular linked list,若是則將現有所有節點內容排序,否則不做事 * `(b)` 建立兩個節點並且安插在結尾,內容分別是 `value1` 和 `value2` * `(c)` 建立兩個節點並且安插在開頭,內容分別是 `value1` 和 `value2` * `(d)` 找到節點內容為 `value2` 的節點,並在之前插入新節點,內容為 `value1` * `(e)` 找到節點內容為 `value2` 的節點,並在之後插入新節點,內容為 `value1` * `(f)` 找到節點內容為 `value1` 的節點,並在之前插入新節點,內容為 `value2` * `(g)` 找到節點內容為 `value1` 的節點,並在之後插入新節點,內容為 `value2` * `(h)` 尋找所有節點,當遇到符合給定數值 `value1` 和 `value2` 的兩個節點時,將這兩個找到的節點相互串接 在程式輸出中,訊息 `Traversal in forward direction` 後依序印出哪幾個數字呢? Z1 = ? * `(a)` 63 * `(b)` 86 * `(c)` 51 * `(d)` 48 * `(e)` 72 * `(f)` 這個程式有缺陷,無法正確輸出數字 Z2 = ? * `(a)` 63 * `(b)` 86 * `(c)` 51 * `(d)` 48 * `(e)` 72 * `(f)` 這個程式有缺陷,無法正確輸出數字 Z3 = ? * `(a)` 63 * `(b)` 86 * `(c)` 51 * `(d)` 48 * `(e)` 72 * `(f)` 這個程式有缺陷,無法正確輸出數字 Z4 = ? * `(a)` 63 * `(b)` 86 * `(c)` 51 * `(d)` 48 * `(e)` 72 * `(f)` 這個程式有缺陷,無法正確輸出數字 Z5 = ? * `(a)` 63 * `(b)` 86 * `(c)` 51 * `(d)` 48 * `(e)` 72 * `(f)` 這個程式有缺陷,無法正確輸出數字 在程式輸出中,訊息 `Traversal in reverse direction` 後依序印出哪幾個數字呢? Z6 = ? * `(a)` 63 * `(b)` 86 * `(c)` 51 * `(d)` 48 * `(e)` 72 * `(f)` 這個程式有缺陷,無法正確輸出數字 Z7 = ? * `(a)` 63 * `(b)` 86 * `(c)` 51 * `(d)` 48 * `(e)` 72 * `(f)` 這個程式有缺陷,無法正確輸出數字 Z8 = ? * `(a)` 63 * `(b)` 86 * `(c)` 51 * `(d)` 48 * `(e)` 72 * `(f)` 這個程式有缺陷,無法正確輸出數字 Z9 = ? * `(a)` 63 * `(b)` 86 * `(c)` 51 * `(d)` 48 * `(e)` 72 * `(f)` 這個程式有缺陷,無法正確輸出數字 Z10 = ? * `(a)` 63 * `(b)` 86 * `(c)` 51 * `(d)` 48 * `(e)` 72 * `(f)` 這個程式有缺陷,無法正確輸出數字 **解題策略** * FuncA 中 `if (!*start)` 會使 *start 指向新建立的節點,否則會透過 `last = (*start)->prev;` 找到最後一個節點,並將新建立的節點放在最後一個節點後。所以答案為 `(e) 建立新節點,內容是 value,並安插在結尾` * FuncB 所有操作大致與 FuncA 相同,都是找到原本最後一個節點並安插一個新節點再它後面。但唯一不同的是最後一句 `*start = new_node;` 將 *start 指向新節點,因此答案應為 `(d) 建立新節點,內容是 value,並安插在開頭` :::warning FuncB 中少了和 FuncA 中有的 `if (!*start)` 判斷式,如果沒有再 initial 時將 prev & next 指向自己可能會發生錯誤存取的問題 ::: * FuncC 先建立數值為 value1 的節點,再以 tmp 從 start 開始 trace 每個節點,直到節點中數值為 value2。並將節點加到這個節點後。因此答案應為 `(e) 找到節點內容為 value2 的節點,並在之後插入新節點,內容為 value1` * 根據上面的分析,main() 中的 Func() 會使 *start 指向數列 `48->51->63->72->86` * display() 則是將傳入的 pointer to pointer 指向的數列從頭印到尾再從尾印到頭,因此 `Z1~Z10` 分別為 `48 51 63 75 86 86 72 63 51 48` ### 測驗 `2` ![](https://i.imgur.com/KKSlhnO.png) 考慮以下程式碼,推敲程式作用並分析輸出。 假設條件: * `malloc` 總是成功而且返回的記憶體空間可讀寫 * `malloc()` 得到的地址成嚴格單調遞增函數 ```Clike #include <stdio.h> #include <stdlib.h> /* Link list node */ struct node { int data; struct node *next; }; int FuncX(struct node *head, int *data) { struct node *node; for (node = head->next; node && node != head; node = node->next) data++; return node - head; } struct node *node_new(int data) { struct node *temp = malloc(sizeof(struct node)); temp->data = data; temp->next = NULL; return temp; } int main() { int count = 0; struct node *head = node_new(0); head->next = node_new(1); head->next->next = node_new(2); head->next->next->next = node_new(3); head->next->next->next->next = node_new(4); printf("K1 >> %s\n", FuncX(head, &count) ? "Yes" : "No"); head->next->next->next->next = head; printf("K2 >> %s\n", FuncX(head, &count) ? "Yes" : "No"); head->next->next->next->next->next = head->next; printf("K3 >> %s\n", FuncX(head, &count) ? "Yes" : "No"); head->next = head->next->next->next->next->next->next->next->next; printf("K4 >> %s\n", FuncX(head, &count) ? "Yes" : "No"); printf("K5 >> %d\n", head->next->data); printf("count >> %d\n", count); return 0; } ``` ==作答區== `FuncX` 的作用是 (涵蓋程式執行行為的正確描述最多者) * `(a)` 走訪 circular linked list 所有節點,計算節點數量並更新 * `(b)` 走訪 circular linked list 所有節點,計算節點數量並更新,回傳最後一個節點和開頭的地址距離 (offset) * `(c)` 走訪 circular linked list 所有節點,回傳最後一個節點和開頭的地址距離 (offset) * `(d)` 判斷是否為 circular linked list,若為 circular 則回傳非零值,其他回傳 `0` * `(e)` 判斷是否為 circular linked list,若為 circular 則回傳 `0`,其他非零值 * `(f)` 判斷是否為 circular linked list,若為 circular 則回傳 `0`,其他非零值,過程中計算走訪的節點總數 * `(g)` 判斷是否為 circular linked list,若為 circular 則回傳非零值,其他回傳 `0`,過程中計算走訪的節點總數 `K1 >>` 後面接的輸出為何 * `(a)` No * `(b)` Yes `K2 >>` 後面接的輸出為何 * `(a)` No * `(b)` Yes `K3 >>` 後面接的輸出為何 * `(a)` No * `(b)` Yes `K4 >>` 後面接的輸出為何 * `(a)` No * `(b)` Yes `K5 >>` 後面接的輸出為何 * `(a)` 5 * `(b)` 4 * `(c)` 3 * `(d)` 2 * `(e)` 1 * `(f)` 0 `count >>` 後面接的輸出為何 * `(a)` 5 * `(b)` 4 * `(c)` 3 * `(d)` 2 * `(e)` 1 * `(f)` 0 ## 第五周(上) * [題目](https://hackmd.io/s/SynmEVIqG) ### 測驗 1 考慮以下程式碼,推敲其作用 (選出最符合行為的描述) ```clike uint8_t Func32(uint32_t x) { return x ? Func32(x >> 1) - 1 : 32; } ``` 將 `Func32` 改寫為以下功能等價的程式碼: ```clike uint8_t FuncI(uint32_t x) { if (!x) return 32; int n = 1; if (!(x >> 16)) { n += 16; x <<= 16; } if (!(x >> M1)) { n += 8; x <<= 8; } if (!(x >> M2)) { n += 4; x <<= 4; } if (!(x >> M3)) { n += 2; x <<= 2; } n = n - (x >> 31); return n; } ``` ==作答區== Func32 = ? * `(a)` 計算對 `x` 的二進位表示法中,總有有幾個 bit 為 `1`,如果全部都是 `1`,那麼回傳 `32` * `(b)` 計算對 `x` 的二進位表示法中,總有有幾個 bit 為 `0`,如果全部都是 `0`,那麼回傳 `32` * `(c)` 計算輸入數值 `x` 的二進位表示中,回傳開頭 (從高位到低位元) 有幾個 0,倘若 `x` 為 `0x0`,則輸出 32 * `(d)` 計算輸入數值 `x` 的二進位表示中,回傳開頭 (從高位到低位元) 有幾個 1,倘若 `x` 每個 bit 均為 `1`,則輸出 32 * `(e)` 計算輸入數值 `x` 的二進位表示中,回傳尾巴 (低位元到高位) 有幾個 1,倘若 `x` 每個 bit 均為 `1`,則輸出 32 * `(f)` 計算輸入數值 `x` 的二進位表示中,回傳尾巴 (低位元到高位) 有幾個 0,倘若 `x` 為 `0x0`,則輸出 32 M1 = ? * `(a)` 0xF * `(b)` 0xFF * `(c)` 0x12 * `(d)` 0x14 * `(e)` 0x16 * `(f)` 0x18 * `(g)` 0x1A M2 = ? * `(a)` 0x1F * `(b)` 0x1E * `(c)` 0x1D * `(d)` 0x1C * `(e)` 0x1B * `(f)` 0x1A * `(g)` 0x10 M3 = ? * `(a)` 0x1E * `(b)` 0x1D * `(c)` 0x1C * `(d)` 0x1B * `(e)` 0x1A * `(f)` 0x10 **解題策略** * Func32 = ? 將極端值代入 `Func32` 中,x 為 0x00 時會得到 32,x 為 0xFF 會得到 0。`return x ? Func32(x >> 1) - 1 : 32;` 中, `Func32(x >> 1)` 最終一定會使傳入參數為 0,return 32。 而每次呼叫 `Func32(x >> 1)` 就會 -1,代表==從 x 往右 shift n 次就會變成 0,則最終答案就是 32-n== 。因此答案為 `(c) 計算輸入數值 x 的二進位表示中,回傳開頭 (從高位到低位元) 有幾個 0,倘若 x 為 0x0,則輸出 32` * M1 = ? if (!(x >> M1)) 則 n += 8 ,代表將 x 往右移 M1 位後若 ==0,就代表 x 的左邊有 8 bits 的 0。而 x 為一個 32 位元數,因此答案應該為 ==32-8=24== ,轉換成 16 進位為 `(f)0x18` * M2 = ? 同上題,答案應為 ==32-4=28== ,轉換成 16 進位為 `(c)0x1C` * M3 = ? 同上題,答案應為 ==32-2=30== ,轉換成 16 進位為 `(c)0x1E` ## 第五周(中) * [題目](https://hackmd.io/s/HkQjgqI5G) </br>**先大膽假設跟 ASCII 有關** ![ ](https://www.asciitable.com/index/asciifull.gif) * 發現將 `double m[] = { 3823806048287157.0,96 }` 改為 `double m[] = {3823806048287157.0}` 或 `double = 3823806048287157.0` 都不影響結果 * 將 `3823806048287157.0` 丟到 [double & binary converter](http://www.binaryconvert.com/convert_double.html) 中,得到 * binary 01000011 00101011 00101011 01110110 01110010 01100101 01110011 01101010 * hex 0x432B2B767265736A * `jserv++C` 共 8 個字元,因此應該是每個字元對應到 hex 的兩個字元 >[color=green] 在解題的時候發現 `j` 的 ascii code 應該為 `6A` 而不是 hex 轉換後的前兩碼 `43` ,~~瞎猜~~思考了好一陣子才想到應該是 little endian 的關係[name=bauuuu1021] * 將 hex 兩個字元為一組,反向排列後得到 6A 73 65 72 76 2B 2B 43,也確實為 jserv++C hex 編碼的 ascii code,**也證實剛開始的假設正確** :::success 延伸題目: 1. 修改程式,允許輸出你的 GitHub 帳號名稱 2. 承上,修改 gen() 函式,透過特定的轉換,讓 `m[]` 的內容變為你的英文姓氏 (last name),程式碼應該越短越好,而且不得以字串的形式存在於原始程式碼 3. 如果在 big-endian 的環境中,要達到一樣的效果,我們該如何改寫? ::: 1. 將我的帳號 `bauuuu1021` 反向排序並對應到 ascii 為 `31 32 30 31 75 75 75 75 61 62`(hex) ,[轉成](https://gregstoll.dyndns.org/~gregstoll/floattohex/) double[ ] 並輸出卻只得到 ``` bauuuu1021@X555LB:~$ ./test bauuuu10 ``` * 發現 `bauuuu10` 剛好 8 字元,再加上 ascii 字串去掉最前面的 `31 32` 所得到的 double 值也會相同 * 因此將 `31 32` 另外轉換成一組 double 值並放到 struct 中原本數值的後面,得到結果: ``` bauuuu1021@X555LB:~$ cat test.c #include <stdio.h> int main() { puts((char *) &(double []){ 1.507773427734912e-76,6.2223e-320 }); } bauuuu1021@X555LB:~$ ./test bauuuu1021 ``` 2. * jserv++C:0 10000110010 1011001010110111011001110010011001010111001101101010 * bau:0 00000000000 0000000000000000000000000000011101010110000101100010 3. 在將字串轉成 ascii 前不需將整個字串轉置,直接轉成 ascii 即可 ## 第五周(下) * [題目](https://hackmd.io/s/Sk30MXDqM) ![](https://i.imgur.com/Y80lxhP.png) ### 測驗 1 * Q11: 做了 1: Stuck at 0 left of cut 的修改,`sw $s1, 0($s2)` 能否運作? * Q12: 承上,下方程式能否運作? ``` lw $s1, 0($s2) add $s1,$2,$3 ``` **解題策略** 題目所切掉的是控制是否 write back to register 的線,所以有要寫回到 register 的指令會失效,因此: Q11:沒有要寫回,`可以運作` Q12:要寫回,`不可運作` ### 測驗 2 Q21: 做了 2: Cut here 的修改後,以下程式能否運作? ``` add $s1, $s1, $s1 add $s1, $t0, $t1 ``` **解題策略** 切斷處並不影響 $rs 及 $rt 輸入到 ALU,而是影響到從 ALU 執行完的 forwarding 的其中一條;但題目的程式並不會用到 forwarding ,所以 `可以運作` ### 測驗 3 * Q31: 做了 3: Cut here 的修改後,以下程式能否運作? (exit 為某個副程式) ``` cmp: xor $t1, $s2, $s1 slt $t2, $zero, $t1 sll $t2, 2 add $ra, $ra, $t2 jr $ra entry: addi $s2, $zero, 2 addi $s1, $zero, 2 jal cmp j exit ``` * Q32: 承上,以下程式能否運作? ``` addi $s2, $zero, 2 addi $s1, $zero, 2 beq $s2, $s1, exit ``` **解題策略** * 圖 1 ![](https://i.imgur.com/ivC8jrU.jpg) * 圖 2 ![](https://i.imgur.com/BZM38ES.jpg) * PC 位址跳躍主要可分為 `j` 及 `beq` 兩種類型,從圖 1 和圖 2 與題目做比較可得知,題目所切掉的地方只對 `beq` 有影響,對 `j` 則無。 * 因此 Q31 `可以運作`, Q32 `不可運作`。 ###### tags: `bauuuu1021`, `sysprog`