# D06: c-review
contributed by < `rex662624` >
## [第 4 週測驗題](https://hackmd.io/s/SyK-WApKM)
### 測驗 `1`
#### 解題
##### 1. FuncA 的作用是
```clike=
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;
}
```
這題觀察程式碼,關鍵是在第12行的部分,做了:插入的 node 下一個 node 是 start 。剩下的13~15行則是做了把插入的 node 放到合適的位置與把指標只好。因此 start 仍然為本來的 start ,但是start 前面卻多了一個 node,也就是在結尾的位置加入一個 node 。
而 2~8 行,則是在做尚未有任何一個 node 的狀況,因此在結尾插入的 node 也等於是 start。
故答案是: `(e)` 建立新節點,內容是 `value`,並安插在結尾
##### 2. FuncB 的作用是
```clike=
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;
}
```
這題本來我在想是不是與 funcA 是相同的。後來發現,差別在於第8行,它把 start 指向了新 node,因此是在開頭的地方插入新 node 。
答案是`(d)` 建立新節點,內容是 `value`,並安插在開頭
##### 3. FuncC 的作用是
```clike=
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;
}
```
首先第3行把新 node 值設為 value1 。而後從 start 一路找到值 = value2 的 node ,而後將 value1 的 node 放在 value2 node後面。
答案是 `(e)` 找到節點內容為 `value2` 的節點,並在之後插入新節點,內容為 `value1`
不過這裡好像有幾個沒有考慮的點:
1. 若是 start 是指向 NULL ,也就是尚未有任何 node 的時候,則程式應該會 core dumped ,因為有 temp->data 這個動作。
2. 若是根本沒有 value2 這個值,會進入無限迴圈,因為這是 circular linked list ,一路 next 到尾巴後又會從頭開始,永遠無法結束。因此user 必須自己保證 list 內一定有 value2 。
##### 4. 在程式輸出中,訊息 `Traversal in forward direction` 後依序印出哪幾個數字呢?
首先先看main
```clike=
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;
}
```
做到第 5 行結束時, list 是長這樣
```
[48]<->[51]<->[63]<->[72]<->[86]
```
觀察`Traversal in forward direction` 後做了
```clike=
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);
```
因此是從開頭一路印出 data,直到 start 的 prev 就跳出,因為最後又有印一次(第五行),所以本來 for 沒有印出的最後一個 data 會被這行印出來。
因此答案順序為 48 51 63 72 86 。
##### 5. 在程式輸出中,訊息 `Traversal in reverse direction` 後依序印出哪幾個數字呢?
同樣的, list 是
```
[48]<->[51]<->[63]<->[72]<->[86]
```
程式碼:
```clike=
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");
```
這次是從 start 的前一個人開始印,一路往 prev 走直到走到 start ,這裡 start 並不會在 for 裡面印出來,而是跳出。直到第5行因為又做了一次 print ,才被印出來。
因此答案是 86 72 63 51 48
### 測驗 `2`
#### 解題
##### 1. `FuncX` 的作用是
首先觀察程式碼
```clike=
int FuncX(struct node *head, int *data) {
struct node *node;
for (node = head->next; node && node != head; node = node->next)
data++;
return node - head;
}
```
這裡看到 for 裡面,首先指向 head 的下一人,如果 node 是 NULL 或是 head 就會跳出,最後 return node - head 。在這裡如果是由於 NULL而跳出,則 node - head 不會是 0 ,而如果是因為回到 head 而跳出,則最後return 的會是 head 的 address 減掉自己,所以會是 0。而這也代表它是 circular linked list 。
因此答案是`(e)` 判斷是否為 circular linked list,若為 circular 則回傳 `0`,其他非零值
##### 2. ~ 6. `K1 >>` ~ `K5 >>`後面接的輸出為何
```clike=
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;
}
```
首先必須要知道 node_new() 是如何運作。發現裡面只是要一塊空間並把值填入,然後把 next 指向 NULL 。
首先 9~13 行的圖形將會是以下:
```
9 head->[0]
10 head->[0]->[1]
11 head->[0]->[1]->[2]
12 head->[0]->[1]->[2]->[3]
13 head->[0]->[1]->[2]->[3]->[4]
```
因此`printf("K1 >> %s\\n", FuncX(head, &count) ? "Yes" : "No");` 會印出YES,因為這裡會回傳非0值,所以等於if ( true ) ,會印出前面的YES。**K1=YES**。
再來繼續觀察程式碼
```clike=
head->next->next->next->next = head;
printf("K2 >> %s\\n", FuncX(head, &count) ? "Yes" : "No");
```
本來的list是`head->[0]->[1]->[2]->[3]->[4]` 。`head->next->next->next->next `是[3]的next,因此[3]又會指向[0]成為 circular list,則 **K2=NO**。(但這裡[4]有 memory leak)
再處理K3
```clike=
head->next->next->next->next->next = head->next;
printf("K3 >> %s\\n", FuncX(head, &count) ? "Yes" : "No");
```
現在的list是head->[0]~[3]的單向 circular list 。而等號左邊的是[0]的next,等號右邊的也是[0]的next,因此list不變,這裡同樣**K3=NO**。
```clike=
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);
```
現在的list是head->[0]~[3]的單向 circular list 。而這裡等號左邊是[0]的next,要指向的是[0],因此[0]自己指向自己,這裡同樣[1]~[3]會造成 memory leak 。因此最後也是一個circular linked list **K4=NO**
K5: head ([0]) 的next仍為[0]自己,因此印出的**K5=0**。
##### 7. `count >>` 後面接的輸出為何
這裡也同樣解釋了為何 1.並非 f 而是 e的原因。function 內宣告的 parameter 是 `int *data` ,而程式碼走訪每個 node 後做的是 data++; 這裡如果要輸出到底有多少 node 應該用`*data++`,而這裡 data++只不過加了 address ,而且因為對 address 是 pass by value ,所以並不會養想到 function scope 外面的任何值。
因此最後 count 仍然為原來的 0 。**K5=0**。
## [第 5 週測驗題 (上)](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 = ?**
觀察程式碼,發現做的動作是 recursive call Func32,途中 x 會不斷的右移 1 bits,直到最後 x 已經 =0,會 return 32 ,再一步一步的往上 -1 。而這個動作的意義在,右移到全部都為0了,就表示剛才 recursive call 的次數就等於尾數有幾位不為0。因此用32減掉尾數不為0有幾位,我們就可以得知開頭有幾個0。
==Ans:計算輸入數值 x 的二進位表示中,回傳開頭 (從高位到低位元) 有幾個 0,倘若 x 為 0x0,則輸出 32==
**M1、M2、M3=?**
參考[ st9007a 的 clz ](https://hackmd.io/s/rywfL4tj-)。
這裡是利用 if 來分別確認 `x` 的前 16 , 8 , 4 , 2 個 bits 是否為零,如果為零則將他們加入 `n`,並且左移 `x` 確保下個 if 能檢查到正確的位置,如果不為零則直接做下一個 if 檢查,最後 `x` 剩下 最前面的 1 bit 沒有被檢查到,在第9行做處理,如果是0就等於不做動作,如果是1就減掉1。~~第9行其實也可以在一開始做處理,多寫一個`if(x>>31==1)retrun 0 ;`,就可以把第9行刪掉。~~
因此答案是==M1=24 M2=28 M3=30==
>你這邊好像想錯了,n 一開始設為 1,所以所有"開頭有偶數個 0 "的情況第九行都是有意義的(x>>31=1)否則無法輸出偶數的 n,例如:
>開頭有 16 個 0 的話會跑if (!(x >> 16)) { n += 16; x <<= 16; }
>跑完後 n = 17, x = 10..0,到第九行 n = 17 - 1 = 16
[name=e94046165][color=green]
>>
>>感謝指正!的確 x 在中間就會被改值了。所以你說的是對的,若是偶數個 0 的狀況,因為 n 初值是1,最後跑完 if 就都會多1,要在第9行做處理。
>>[name=rex662624][color=#662624]
### 測驗 `2`
#### 解題
**Func64 的作用為?**
```clike=
union u_qword {
struct {
uint32_t low;
uint32_t high;
} dwords;
uint64_t qword;
};
struct udiv_result {
union u_qword q;
union u_qword r;
};
static inline int Func64(uint64_t val)
{
uint32_t lo = (uint32_t) val;
uint32_t hi = (uint32_t) (val >> 32);
return hi ? Func32(hi) : 32 + Func32(lo);
}
```
這裡將測驗1的 Func32 擴展為 64-bit 版本。方法是先把 64bits 分為前後 32bits,分別為 hi 和 lo 。 然後第18行判斷最高 32bits 是不是皆為0,若不是的話就呼叫 Func(32) 看看到底有0在最高位。如果前 32bits 都是0,則是用32+最低位又有幾個 leading 0 算出答案。
答案是==計算輸入數值 x 的二進位表示中,回傳開頭 (從高位到低位元) 有幾個 0,倘若 x 為 0x0,則輸出 64==
```clike=
static int do_udiv32(uint32_t dividend,
uint32_t divisor,
struct udiv_result *res)
{
/* Ensure dividend is always greater than or equal to the divisor. */
uint32_t mask = Func32(divisor) - Func32(dividend);
divisor <<= mask; /* align divisor */
mask = 1U << mask; /* align dividend */
do {
if (dividend >= divisor) {
dividend -= divisor;
res->q.dwords.low |= mask;
}
divisor >>= 1;
} while ((P1) && dividend);
res->r.dwords.low = dividend;
return 0;
}
int udiv64(uint64_t dividend, uint64_t divisor, struct udiv_result *res)
{
uint64_t mask;
uint64_t bits;
res->q.qword = res->r.qword = 0;
if (divisor == 0) { /* division by 0 ? */
res->q.qword = 0xffffffffffffffffull;
return -1;
}
if (divisor == dividend) {
res->q.qword = 1;
return 0;
}
if (divisor > dividend) {
res->r.qword = dividend;
return 0;
}
/* only 32 bit operands that the preconditions are fulfilled. */
if (!(divisor >> 32) && !(dividend >> 32))
return do_udiv32((uint32_t) dividend, (uint32_t) divisor, res);
bits = Func64(divisor) - Func64(dividend);
divisor <<= bits; /* align divisor and dividend */
mask = 1ULL << bits;
/* division loop */
do {
if (dividend >= divisor) {
dividend -= divisor;
res->q.qword |= mask;
}
divisor >>= 1;
mask >>= 1;
} while ((P2) && dividend);
res->r.qword = dividend;
return 0;
}
```
首先理解 do_udiv32 ,這裡用到的演算法與計算機組織用到的有點相似。
mask 是表示最後的商值有幾位數。do 裡面的演算法為,若 dividend > divisor ,dividend 就減去 divisor ,而最後的中止條件為直到(~(P1) | dividend==0) 這裡後面的 divided==0 是整除的情況跳出機制,所以可以推測 ~(P1) 是不整除的情況,也就是要用到 mask 來看商數是否已經夠長了。所以用 mask>>1 ,每次 while 迴圈算出一位數就右移一位,直到最後 mask \=\=0就跳出,因此==P1\:mask>>\=1==
同理可以推廣到下面的 64-bit 版本。這裡加上了一些除錯的機制。例如第27行是用來偵測是否除以0。
而這裡的答案==P2=bits - -==,與 32-bit 的跳出機制相同,都是偵測商數是否已經到達目標位數。
```clike=
int main()
{
char digitbuff[20];
char *pos = digitbuff + sizeof(digitbuff);
union u_qword v; /* current value */
union u_qword nv; /* next value */
struct udiv_result d;
int64_t value = 0xCAFEBABE; /* Java classfile magic number */
v.qword = (unsigned long long) value;
while (v.dwords.high != 0) { /* process 64 bit value as long as needed */
/* determine digits from right to left */
udiv64(v.qword, 10, &d);
*--pos = d.r.dwords.low + P3;
v.qword = d.q.qword;
}
do { /* process 32 bit (or reduced 64 bit) value */
nv.dwords.low = v.dwords.low / P4;
*--pos = (v.dwords.low - (10 * nv.dwords.low)) + P5;
} while ((v.dwords.low = nv.dwords.low) != 0);
int len = digitbuff + sizeof(digitbuff) - pos;
char *p = pos;
while (len--)
putchar(*p++);
printf("\n");
return 0;
}
```
首先P3和P5,都是做一樣的動作,把原本的數字存進 char 裡面,而這裡要把原本的數字變成 ascii 碼,所以要加上(48)~10~=(0x30)~16~。這裡參考[e94046165 同學](https://hackmd.io/6WXM0FXOR3C8T_E3f501Ww?view#%E6%B8%AC%E9%A9%97-21)找出的錯誤。
最後 P4 的部分,是因為我們要印出十進位,所以我們每次都要除10,如同我們如果要 1bit 1bit 印出二進位值,必須要用 number>>1(就是number/2)一樣。
## [第 5 週測驗題 (中)](https://hackmd.io/s/HkQjgqI5G)
### 解題
首先理解題目`jserv++C` :
`puts((char *) &(double []){ 3823806048287157.0, 96 });`這行,把`3823806048287157.0`這個數字用 char * 型態印出來,因此透過[轉換](http://www.binaryconvert.com/convert_double.html)我們知道了在記憶體內的真實儲存資料為==01000011 00101011 00101011 01110110 01110010 01100101 01110011 01101010==
而因為一個字元就佔了 1byte ,所以我們可以知道上面的數字要用 8 bits為一個單位切割。透過[轉換](https://codebeautify.org/binary-string-converter)我們知道,答案會是 ==C++vresj== 。但這是在big-endian的狀況,而我們的環境若是little-endian,最大的 8 bit 反而會儲存在最後面。也就是其實答案是倒過來的==jserv++C==。
回過頭來看題目,實際上是把`3823806048287157.0`乘上了96次的2,因此在 IEEE-754 double-precision 等於是把次方的11個 bits 加上了96,因此影響的只有前兩個 byte ,也就是`+C`的部份。原本的 前兩個byte是 01000011 00101011 ,次方的11 bits 為1000011 0010,加上96之後會成為1001001 0010,也就是前兩個byte是 01001001 00101011 ,最後整個數字轉為 ascii 後就成為==I++vresj==,在 little-endian 印出的就是 ==jserv++I==
### 延伸題目[(github)](https://github.com/rex662624/2018D06-c-review/tree/master/W5_mid)
#### 修改程式,允許輸出你的 GitHub 帳號名稱
程式碼如下:
```clike=
#include <stdio.h>
int main() {
printf("%s",(char *) &(double []){8.2330057975734170815348646124E-67, 96 });
puts((char *) &(double []){6.0134700169990949837211519873E-154, 96 });
}
```
我的 id 是 rex662624 ,因此64 bits是無法印出來的,因此需要兩行才能印出來。我的策略是:第一個數字先印出rex66262,第二個數字則印出4。
因次透過上面那兩個轉換網址,[ double-precision to deciaml ](http://www.binaryconvert.com/result_double.html?hexadecimal=2020202020202034),[ string to binary ](https://codebeautify.org/string-binary-converter),可以知道 rex66262 換成 decimal 是 8.2330057975734170815348646124E-67 ,而因為第二個字串只要印出4,我的 6.0134700169990949837211519873E-154 其實印出的是 =="4_______"== (去掉底線),後面有七個空白,這樣剛好滿足 8 bytes。
如此一來,就能成功印出 rex662624 並換行。
#### 修改 gen() 函式,透過特定的轉換,讓 `m[]` 的內容變為你的英文姓氏 (last name)
jserv++C :`0` `10010010010` `1011001010110111011001110010011001010111001101101010`
rex_____: `0` `01000000010` `0000001000000010000000100000011110000110010101110010`
第一行是題目字串與它在記憶體的儲存方式,第二行則是目標產生的字串。
因此我把它分成三個部份處理,signed,exponent,mantissa。
首先第一個部份,因為 signed 一樣,所以不需要做處理。
第二個部份是 exponent,我採用了與原來相似的方式,讓它除上656次的2,就相當於把 exponent 減去656,達到我想要的數字。
第三個部份與第二個部份相關,我的方法是直接做加減法成我要的數字,而 ieee754的加減法,次方部份要做對齊才會是對的結果,因此次方的地方, m[0] 已經在第二部份被調整成目標的次方了,這裡就要用目標的次方去作加減法。
如此一來就成功輸出==rex==
```clike=
#include <stdio.h>
double m[] = { 3823806048287157.0, 656 };
void gen() {
if (m[1]--) {
m[0] /= 2;
gen();
} else
{
m[0]-=1.79805224261238405855038489163E44;
puts((char *) m);
}
}
int main() { gen(); return 0; }
```
#### 如果在 big-endian 的環境中,要達到一樣的效果,我們該如何改寫?
* 就範例程式提供的 jserv++C 來說:
1. 因為由上面的轉換,我們知道轉換出來的答案其實是 C\+\+vresj ,因此只要存入 jserv\+\+C 的數值,在big-endian 的環境就能達到正確的效果。
2. 參考[ stackoverflow ](https://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian)。也可以不改變數值,但是另外用一個函式ReverseFloat() 一個一個 char 轉過來。(注意網址中是只有32 bits ,因此只有 4個 char 空間,而若是適用於這題需要 0~7 ,8個空間。)
* 就題目主體gen()來說:
1. 與上一題的2.相同,gen()完再呼叫 ReverseFloat() 把它一個一個char轉過來。
2. 因為本來 C\+\+vresj ,改為 I\+\+vresj ,改變的是最前面的一個 byte 數值而已。而若是在 big-endian 的環境下則改變的是最後一個 byte 。因此不關 exponent 的事 ,而是要改 mantissa 部分,因此要用加減法去做改變 mantissa。
## [第 5 週測驗題 (下)](https://hackmd.io/s/Sk30MXDqM)
考慮某款 MIPS 實作,其 data path 如下:
![](https://i.imgur.com/Y80lxhP.png)
上圖紅色的 `1`, `2`, `3` 對 MIPS 做了對應的修改,請考慮以下狀況,分別解釋對應的程式碼是否能運作。
### 解題
做這個測驗時,發現計算機組織忘記許多,備受打擊。但忘記就跟不會一樣意思,因此也不需要找藉口,只好回到白算盤尋覓過去的時光。
#### Q11:
**做了 `1: Stuck at 0 left of cut` 的修改,`sw $s1, 0($s2)` 能否運作?**
![](https://i.imgur.com/s9bYQ24.png)
根據上面的這張圖(參考連結在最下方),知道了這條線是控制 MemtoReg RegWr 這兩條訊號線,因此若沒有了這條線,Reg永遠不能被寫入, lw 、 add 等需要寫入 reg 的指令都會失效。
而sw不會失效,因為他不牽涉到寫回 reg 的動作。因此這題的答案是==可以運作==。
#### Q12:
**承上,下方程式能否運作?**
```
lw $s1, 0($s2)
add $s1,$2,$3
```
因為 lw 牽涉到寫回 reg ,而因為切掉這條訊號線導致 reg 不能被寫入,因此這個指令不能被運作。(第二個指令也不能運作,因為 add 需要加起來後把答案寫入 reg $s1)。答案是==不能運作==
#### Q21:
**做了 `2: Cut here` 的修改後,以下程式能否運作?**
```
add $s1, $s1, $s1
add $s1, $t0, $t1
```
這題的訊號線連接到的這個 mux ,是用來做選擇進入 ALU 的第一個 operand 來自 指令的部分,抑或是 Data forwarding 取上一個指令的值,這裡必須注意的是,只切了其中一條,因此 $Rs 或 $Rt 其中一個仍可以正常 Forwarding。
回頭看題目發現,這裡並不需要 Data forwarding ,因為這裡沒有 Data Hazard 發生,因此這題的答案為==可以運作==
#### 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
```
觀察3這條訊號,發現它是傳送 PC+4+(imm\*4) 的值,而最後會送到 program counter 前的 mux 選擇要取的值。因此若把這條線去掉了,則會使 beq bne 這種需要 PC+4+(imm\*4) 的 branch 指令失效。
回到題目發現這題沒有用到 bne beq 這類指令,而是用 jump ,因此這題答案是==可以運作==。
#### Q32:
**承上,以下程式能否運作?**
```
addi $s2, $zero, 2
addi $s1, $zero, 2
beq $s2, $s1, exit
```
這裡有用到 beq ,因此答案是==無法運作==。
## 參考資料
* [計算機組織結構](https://hackmd.io/s/H1sZHv4R#)
* 白算盤書 & 計算機組織林英超老師上課講義