owned this note
owned this note
Published
Linked with GitHub
2018q1 Homework3 (c-review)
===
contributed by < `TingL7` >
## 第 4 週測驗題 測驗 `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` 的作用是
* 由開頭的 if 與第 14 行的可以發現,這個函式主要的功能是在建立新節點
* 新節點的 `next` 是 `*start` , `prev` 是 `last` ,且 `*start` 沒有變動,因此新節點是安插在結尾。
* `FuncA` :建立新節點,內容是 `value`,並安插在結尾
* `FuncB` 的作用是
* 基本架構與 `FuncA` 差不多
* 關鍵在於第 29 行 `*start = new_node;` 改變了開頭為新節點,也就是說,新節點是安插在開頭
* `FuncB` :建立新節點,內容是 `value`,並安插在開頭
* `FuncC` 的作用是
* 第 33~34 行在建立內容為 `value1` 的新節點
* 第 35~38 行在尋找內容為 `value2` 的節點
* 第 39~42 行在找到的節點後面插入新節點
* 因此, `FuncC` :找到節點內容為 `value2` 的節點,並在之後插入新節點,內容為 `value1`
* 在程式輸出中,訊息 `Traversal in forward direction` 後依序印出哪幾個數字呢?
* `main` 中,依序建立:
* **51**
* **48** 51
* 48 51 **72**
* 48 51 72 **86**
* 48 51 **63** 72 86
* 由 `start` 不斷印出 `next` 的結果:
`48 51 63 72 86`
* 在程式輸出中,訊息 `Traversal in reverse direction` 後依序印出哪幾個數字呢?
* 由 `last` 不斷印出 `prev` 的結果:
`86 72 63 51 48`
## 第 4 週測驗題 測驗 `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` 的作用是 (涵蓋程式執行行為的正確描述最多者)
- [x] 走 circular linked list 所有節點
* for loop 中有走訪過所有節點,但是沒有對節點做任何事
- [x] 判斷是否為 circular linked list
* 最後得到的 node 是 NULL 或 head ,回傳要兩個相減,明顯可以看出兩者的差異在於是否為 circular linked list
- [ ] 計算節點數量並更新
* 改變的是 data 所紀錄的位址,也就是更新的是 count 的位址,並沒有計算節點數量
* 如果要計算節點數量,程式需要修改
```
int FuncX(struct node *head, int *data) {
struct node *node;
for (node = head->next; node && node != head; node = node->next)
(* data)++;
return node - head;
}
```
- [ ] 回傳最後一個節點和開頭的地址距離 (offset)
* 若為 circular 回傳的便不是最後一個節點與開頭的距離
- [ ] 若為 circular 則回傳非零值,其他回傳 0
- [x] 若為 circular 則回傳 0,其他非零值
* 若為 circular 則 node 為 head ,因此回傳 head - head 0
* 若非 circular 則回傳最後一個節點和開頭的地址距離 (offset)
- [ ] 過程中計算走訪的節點總數
* 改變的是 data 所紀錄的位址,也就是更新的是 count 的位址,並沒有計算走訪的節點數量
* 判斷是否為 circular linked list,若為 circular 則回傳 `0`,其他非零值
* `K1 >>` 後面接的輸出為何
* link list
```graphviz
digraph graphname{
rankdir=LR;
0 -> 1;
1 -> 2;
2 -> 3;
3 -> 4;
}
```
* 非 circular linked list ,`FuncX` 回傳 0
* `K1 >> Yes`
* `K2 >>` 後面接的輸出為何
* `head->next->next->next->next = head;`
head 0------>1------>2----->3 **------>** head
* link list
```graphviz
digraph graphname{
rankdir=LR;
head[color=Blue, fontcolor=Red, fontsize=18, shape=box];
head -> 0;
0 -> 1;
1 -> 2;
2 -> 3;
3 -> 0;
4;
}
```
* circular linked list ,`FuncX` 回傳 1
* `K2 >> No`
* `K3 >>` 後面接的輸出為何
* `head->next->next->next->next->next = head->next;`
head 0-------->1------>2------>3------>0 **----->** (head->next 1)
* link list
```graphviz
digraph graphname{
rankdir=LR;
head[color=Blue, fontcolor=Red, fontsize=18, shape=box];
head -> 0;
0 -> 1;
1 -> 2;
2 -> 3;
3 -> 0;
4;
}
```
* circular linked list ,`FuncX` 回傳 1
* `K3 >> No`
* `K4 >>` 後面接的輸出為何
* `head->next = head->next->next->next->next->next->next->next->next;`
head 0 **--------->** head 0------->1------>2------>3------>0------>1------>2------>3------>0
* link list
```graphviz
digraph graphname{
rankdir=LR;
head[color=Blue, fontcolor=Red, fontsize=18, shape=box];
head -> 0;
0 -> 0;
1 -> 2;
2 -> 3;
3 -> 0;
4;
}
```
* circular linked list ,`FuncX` 回傳 1
* `K4 >> No`
* `K5 >>` 後面接的輸出為何
* head->next->data
head 指向自己,因此裡面的值是 0
* `K5 >> 0`
* `count >>` 後面接的輸出為何
* `count` 初始值為 0
* 每次使用 `FuncX` 時,會傳入 `count` 的位址作為參數 `data` ,並在 `FuncX` 中改變 `data` ,但改變後的 `data` 不會回傳,因此實際上 `count` 沒有任何改變
* `count >> 0`
## 第 5 週測驗題 (上) 測驗 `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 = ?
* 從上方的程式碼可以發現
* 如果 `x` 不為 0 的話,每次就會向右移一位,且會傳值會減一,也就是 32 減去遞迴層數。
* 遞迴層數為從高位算來第一個 1 的位置,因此 32 減去遞迴層數即為從高位到低位開頭 0 的數量
* 從下方的程式碼可以發現, Func32 再做的事主要有兩件
* 如果 `x` 為 0 就回傳 32
* `if (!(x >> 16)) { n += 16; x <<= 16; }`
測試前 16 bit 是否為 0 ,是的話 `n` 加 16 ,表示 `n` 計算 0 的數量
* `n = n - (x >> 31);`
因為 `n` 的初始值為 1 ,假設至少有一個 0 ,所以這邊由最高位的值來決定 0 的數量
* 計算輸入數值 `x` 的二進位表示中,回傳開頭 (從高位到低位元) 有幾個 0,倘若 x 為 0x0,則輸出 32
* M1 = ?
* 測試前 8 位是否全為 0,經過 shift 之後,留下 8 位,因此要右移 `32 - 8`
* M1 = 24 = 0x18
* M2 = ?
* 比照 M1 留下 4 位
* M2 = 32 - 4 = 28 = 0x1C
* M3 = ?
* 比照 M1 留下 2 位
* M2 = 32 - 2 = 30 = 0x1E
#### 參考資料
[重新理解數值](https://hackmd.io/s/SkKZBXZT#Count-Leading-Zero)
## 第 5 週測驗題 (上) 測驗 `2`
延伸測驗 `1`,將 `Func32` 應用於以下程式碼,可輸入給定 64-bit 數值的 10 進位表達方式 (類似 `printf` 函式的 `%d` 格式),補完程式碼並推測其作用。
```clike=
#include <stdint.h>
#include <stdio.h>
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);
}
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;
}
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;
}
```
---
### 解題想法 & 思考
* `Func64` 的作用為?
* 已知 `Func32`的功能為 計算輸入數值 `x` 的二進位表示中,回傳開頭 (從高位到低位元) 有幾個 0,倘若 `x` 為 0x0,則輸出 32
* `Func64` 中先將輸入的數字切成兩個 32bit 的 unsigned int,再測試 `val` 的前 32 是否全為 0 ,之後呼叫 `Func32` 得到開頭 0 的數量
* 基本上功能和 Func32 相似,只差在若 `x` 為 0x0 ,輸出64
* 計算輸入數值 `x` 的二進位表示中,回傳開頭 (從高位到低位元) 有幾個 0,倘若 `x` 為 0x0,則輸出 64
* `P1` = ?
```clike=24
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;
}
```
* `do_udiv32()` 的功能: 做 32 位元的除法
* 一開始先利用 mask 對齊除數與被除數
* [1U](https://stackoverflow.com/questions/4192440/is-there-any-difference-between-1u-and-1-in-c): unsigned value 1
* 1ULL: unsigned long long value 1
* do while loop 中變是在做長除法
* 被除數比除數大的話就可以除,不然就一到下一位
* 二進位除法不用考慮倍數,可以用減法做
* 有相減的話,表示該位元的結果為1,因此商 `res` 與 mask 做or
* mask 應該每做完一位,就向右移
* 由上述可知, `P1` = `mask >>= 1`
* `P2` = ?
* `udiv64()` 與 `udiv32()` 在除法的方面很像,最大的不同在於多了一個變數 `bits` ,此變數的功能在於計算商數的位數
* 第 70 行迴圈的條件該是計算到商的最後一位,也就是 bit 為 0
* 因此, `P2` = `bits--`
* `P3` = ?
```clike=93
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;
}
```
* 進行完一次除法後,要將結果存到 `pos` , `pos` 是最後要以 char 印出的結果,因此為了使結果符合 ASCII 要加上 `'0'` 也就是 0x30
* `P3` = `0x30`
* `P4` = ? `P5` = ?
```clike=100
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);
```
* 要計算十進位,因此,迴圈內每次都除 10
* `P4` = `10`
* `pos` 儲存結果,如同 `P3` 要加上 `'0'`
* `P5` = `0x30`
### 延伸題目: 將 10 進位輸出改為 16 進位
* 除以 10 的地方改為除以 16
* 輸出 10 以上要以 ABCDEF 代替,在 ASCII 中相當於加 0x37
```clike=93
while (v.dwords.high != 0) { /* process 64 bit value as long as needed */
/* determine digits from right to left */
udiv64(v.qword, 16, &d);
*--pos = d.r.dwords.low + ((d.r.dwords.low > 9) ? 0x37 : 0x30);
v.qword = d.q.qword;
}
do { /* process 32 bit (or reduced 64 bit) value */
nv.dwords.low = v.dwords.low / 16;
*--pos = (v.dwords.low - (16 * nv.dwords.low)) + (((v.dwords.low - (16 * nv.dwords.low)) > 9) ? 0x37 : 0x30);
} while ((v.dwords.low = nv.dwords.low) != 0);
```
### 延伸題目: 上方程式碼的 `0xCAFEBABE` 為 [magic number](https://en.wikipedia.org/wiki/Magic_number_(programming)),舉出在真實世界中程式的實際作用 (提示: 如 malloc)
* [Java class file](https://en.wikipedia.org/wiki/Java_class_file) 的前 4 byte 為 magic number `CAFEBABE` 主要做用在於辨認 file 是否是能被接受的 class file
* 參考[c語言里malloc的最優實現方式是什麼?](https://www.getit01.com/p20180109059544885/), `malloc()` 中也會設置 magic number,主要是因為避免 free 到沒有 malloc 的位址,藉由 magic number 設置與否就可以檢查
## 第 5 週測驗題 (中) 測驗 `1`
已知在 x86_64 架構,以下程式碼的執行輸出是 `jserv++C`:
```clike
#include <stdio.h>
int main() {
puts((char *) &(double []){ 3823806048287157.0, 96 });
}
```
考慮以下程式碼,假設 puts 函式總是可正確運作,那麼程式輸出為何?
```clike
#include <stdio.h>
double m[] = { 3823806048287157.0, 96 };
void gen() {
if (m[1]--) {
m[0] *= 2;
gen();
} else
puts((char *) m);
}
int main() { gen(); return 0; }
```
---
### 解題想法 & 思考
* `double` 與 `char` 的轉換
* `3823806048287157.0` 轉換成 double 的表示式為 `0x432b2b767265736a`
* 考慮到 x86_64 架構使用 little endianness ,實際上儲存的數字為 `0x6a736572762b2b43`
* char 大小為 1byte ,因此 8byte 的 double 可以切成 8 個 char
根據 [ASCII](https://zh.wikipedia.org/wiki/ASCII)轉換:
|6a|73|65|72|76|2b|2b|43|
|--|--|--|--|--|--|--|--|
|j|s|e|r|v|+|+|C|
* 解讀程式
* `gen()` 的功能為改變 m[0] ,使輸出的字串改變。實際上做的是 m[0] * 2^96^
double 要乘上 2^96^ ,實際上是在 exp 部份加上 96 ,也就是 `0x432 + 0x60 = 0x492`
有改變的地方只有 `0x43` 變成 `0x49` ,也就是 `C` 變成 `I`
* 因此,答案為 `jserv++I`
### 延伸題目:修改程式,允許輸出你的 GitHub 帳號名稱
* GitHub 帳號名稱:`TingL7`
* ASCII: 0x54696e674c37
* 符合 double 有 8byte ,補上兩個空字元: 0x54696e674c370000
* little-endian: 0x0000374c676e6954
* 十進位: 3.003983e-310
* 程式碼
```clike
#include <stdio.h>
int main() {
puts((char *) &(double []){ 3.003983e-310, 96 });
}
```
* 結果:
```
յugL7
```
結果與預期不符,推測是換算成十進位的精度不足, double 的有效位數是16位,精度應取到那
* 修正:
```clike
#include <stdio.h>
int main() {
puts((char *) &(double []){ 3.0039829763669880E-310, 96 });
}
```
* 結果
```
TingL7
```
### 延伸題目:承上,修改 gen() 函式,透過特定的轉換,讓 m[] 的內容變為你的英文姓氏 (last name),程式碼應該越短越好,而且不得以字串的形式存在於原始程式碼
* 英文姓氏: Lai
* ASCII: 0x4c6169
* 符合 double 有 8byte ,補上 5 個空字元: 0x4c61690000000000
* little endian: 0x000000000069614c
* 十進位: 3.4121102345210668E-317
* 目標:`0x0000374c676e6954` 轉換成 `0x000000000069614c`
* 差異
```
TingL7 00000000 00000000 00110111 01001100 01100111 01101110 01101001 01010100
Lai 00000000 00000000 00000000 00000000 00000000 01101001 01100001 01001110
diff 00000000 00000000 00110111 01001100 01100111 00000111 00001000 00011010
```
* ~~進行 bitwise 的操作?~~
* exp部份都是 0 , 不用變動
* 兩者相差 3.0039826351559646E-310
* 程式碼
```clike
#include <stdio.h>
double m[] = { 3.0039829763669880E-310, 3.0039826351559646E-310 };
void gen() {
m[0] = m[0] - m[1];
puts((char *) m);
}
int main() { gen(); return 0; }
```
### 延伸題目:如果在 big-endian 的環境中,要達到一樣的效果,我們該如何改寫?
* 修改輸入,不需要轉換
* TingL7
* little-endian: 0x0000374c676e6954 = 3.0039829763669880E-310
* big-endian: 0x54696e674c370000 = 4.3456679652866146e+98
* Lai
* little-endian: 0x000000000069614c = 3.4121102345210668E-317
* big-endian: 0x4c61690000000000 = 8.7428257608182613e+59
* 差異
* exp: 0x546 - 0x4c6 = 0x080 = 128
* frac: 0x4c696e674c370000 - 0x4c61690000000000 = 4.0279445985412390e+59
* 程式碼
```clike
#include <stdio.h>
int main() {
puts((char *) &(double []){ 4.3456679652866146e+98, 96 });
}
```
```clike
#include <stdio.h>
double m[] = { 4.3456679652866146e+98, 128, 4.0279445985412390e+59 };
void gen() {
if (m[1]--) {
m[0] *= 2;
gen();
} else {
m[0] = m[0] - m[2];
puts((char *) m);
}
int main() { gen(); return 0; }
```
## 第 5 週測驗題 (下)
考慮某款 MIPS 實作,其 data path 如下:
![](https://i.imgur.com/Y80lxhP.png)
上圖紅色的 `1`, `2`, `3` 對 MIPS 做了對應的修改,請考慮以下狀況,分別解釋對應的程式碼是否能運作。
---
### 測驗 `1`
* Q11: 做了 `1: Stuck at 0 left of cut` 的修改,`sw $s1, 0($s2)` 能否運作?
* 可以
* sw
![](https://i.imgur.com/pK8rKKX.png)
* sw 不需要 WB,因此做了 `1: Stuck at 0 left of cut` 也不影響運作
:::info
data memory 要 load 或 store 都需要兩個值, address 及 data ,為什麼圖片上的 data memory 只有一個輸入?
:::
* Q12: 承上,下方程式能否運作?
```
lw $s1, 0($s2)
add $s1,$2,$3
```
* 不可以
* lw
![](https://i.imgur.com/AOFMoEk.png)
* add
![](https://i.imgur.com/ecgnxgc.png)
* lw 和 add 都需要將結果存回暫存器($s1) ,因此做了這個修改後會無法運作
---
### 測驗 `2`
* Q21: 做了 `2: Cut here` 的修改後,以下程式能否運作?
```
add $s1, $s1, $s1
add $s1, $t0, $t1
```
* 可以
* add
![](https://i.imgur.com/jG4quPB.png)
* 這條線的作用在於,當 exe 階段的指令會用到 mem 階段的值,才需要先丟回去運算
* e.g.
```
add $t1, $s1, $s1
sub $s1, $t0, $t1
```
`$t1` 在 `sub` exe 階段時, `add` 的 `$t1` 還沒寫回 reg ,因此 `$t1` 的值是不正確的,修正方法便是將在 mem 階段的結果丟給 sub ,也就是 `2: Cut here` 修改前可以做到的事
---
### 測驗 `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
```
* 不可以
* j([參考圖片](https://userpages.umbc.edu/~squire/cs411_l15.html))
![](https://i.imgur.com/TY3djzM.png)
* 是否切掉 `3` 的線都無法做 jump ,因為在本題圖片的架構中,能夠改變 PC 的只有下一道指令 (PC+4) 以及 branch 的結果
:::info
參考其他同學的答案都是可以的,如果可以運作,在題目的 MIPS 實做是如何運作呢?
:::
* Q32: 承上,以下程式能否運作?
```
addi $s2, $zero, 2
addi $s1, $zero, 2
beq $s2, $s1, exit
```
* 不可以
* beq
![](https://i.imgur.com/5rMhrFP.png)
* beq 所儲存的 exit 是指該指令與 exit 的距離,所以必須加上 PC 才會指向正確的地方,因此如果切斷 `3` 會無法運作
## 第 1 週測驗題 測驗 `5`
考慮以下整數乘法的實作程式碼:
```C
int mul(int n, int m)
{
int ret = 0;
for (int c = 0; m; c++, m /= 2)
if (!(m % 2 - 1)) OP
return ret;
}
```
上述 `OP` 對應到下方哪個程式敘述呢?
---
### 解題想法 & 思考
* 利用直式乘法的概念實作
e.g.
* 1011 * 101 = 110111 (11 * 5 = 55)
```
1011
* 101
---------
1011
0000
+ 1011
---------
110111
```
* 變數
* ret : 乘法結果
* c : 乘數位數
* n : 被乘數
* m : 乘數
* for loop
* if 判斷目前 m 的最後一個 bit 為 1 或 0
* 為 1 的話執行 OP
* 之後捨棄最後一個 bit 並且提升一個位數
* 因此, OP 的內容必須包括
* 移到適當位數的被乘數加上累積下來的結果
* OP = `ret += n << c;`
## 第 3 週測驗題 測驗 `2`
考慮到下方函式 `shift_right_arith` 和 `shift_right_logical` 分別表示算術右移和邏輯右移,請嘗試補完程式碼。可由 `sizeof(int) * 8` 得知整數型態 `int` 的位元數 `w`,而位移量 `k` 的有效範圍在 0 到 `w - 1`。
```Clike
#include <stdio.h>
int shift_right_arith(int x, int k) {
int xsrl = (unsigned) x >> k;
int w = sizeof(int) << P1;
int mask = (int) -1 << (P2);
if (x < 0)
return xsrl P3 mask;
return xsrl;
}
unsigned shift_right_logical(unsigned x, int k) {
unsigned xsra = (int) x >> k;
int w = sizeof(int) << P4;
int mask = (int) -1 << (P5);
return xsra P6 P7;
}
```
---
### 解題想法 & 思考
* P1 = ? P4 = ?
* `sizeof()` 回傳的單位是 byte 但是 `w` 是 `int` 的位元數,因此要乘上 8
* `* 8` 等價於 `<< 3
* 因此, P1 = P4 = 3
* P2 = ? P5 = ?
* mask 是為了做 x 為負數時使用的遮罩
* 參考[Arithmetic Shift Operations](http://www-mdp.eng.cam.ac.uk/web/library/enginfo/mdp_micro/lecture4/lecture4-3-3.html)、[C/C++ 負整數的 shift](https://fcamel-life.blogspot.tw/2011/10/arithmetic-right-shift.html)中,提到C/C++ 沒有定義 signed shift 的行為,也可以透過 compiler 加參數來改變結果。 right shift ,在 [Visual studio](https://msdn.microsoft.com/zh-tw/library/336xbhcz.aspx) 及 [gcc](https://gcc.gnu.org/onlinedocs/gcc-4.3.3/gcc/Integers-implementation.html#Integers-implementation) 中都是在前面補上 sign bit
* mask 的目的為 shift 後,將前面缺漏的 bit 補 1
* 也就是,把前(總 bit 扣掉 位移量)這麼多 bit 補1
* 因此, P2 = P5 = w - k
* P3 = ? (為某種 operation)
* 前 (w - k) 個 bit 必定是 1, mask 符合條件, `xsrl` 這部份全為 0
* 後 w 個 bit 看位移結果 `xsrl`, mask 這部份全為 0
* 因此, 使用 `|` 便能夠合成兩個變數取出需要的部份
* P3 = `|`
* P6 = ? (為某種 operation) P7 = ?
* logical 無論正負前面都要補 0
* 因此,`return xsra & ~mask`