# 2018q1 Homework3 (c-review)
contributed by < `BroLeaf `>
###### tags `BroLeaf`
## [第四週測驗題](https://hackmd.io/s/SynmEVIqG)
### 測驗 `1`
#### 解題
```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;
}
```
1. 可以看出 `FuncA` 一開始有 `if (!*start)` 推斷出這是給第一次 call 用的,直接 malloc 一塊記憶體、設完 node 就 return 了。
後面自然就是第二次以上 call function 用到的部份。
`new_node->next = *start(*start)->prev = new_node;`
新結點的 next 是 start , start 的 prev 是 新結點,知道這是把新加入的結點塞在最後一個結點後面當作新的最後一個。
`FuncA` 就是 建立新節點,內容是 `value`,並安插在結尾
```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;
}
```
2. 這段程式碼跟上段程式碼後半不一樣的地方就是 `*start = new_node;` 他把新結點設為 start ,跟第1題相反。
`FuncB` 是 建立新節點,內容是 `value`,並安插在開頭
```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. value1 是要插入的新結點的值, value2 是用來比對的值。 while loop 是找到與 value1 相同值的結點把他叫作 temp,
`temp->next = new_node;`、`next->prev = new_node;` 修改前後結點的 link ,
`new_node->prev = temp;`、`new_node->next = next;` 設定新結點的 link ,
所以可以看出新結點是在 temp 後、 next 前
`FuncC` 是 找到節點內容為 `value1` 的節點,並在之後插入新節點,內容為 `value2`
```clike=
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;
}
```
4. 按照main call function 的順序
FuncA(&start, 51):51
FuncB(&start, 48):48->51
FuncA(&start, 72):48->51->72
FuncA(&start, 86):48->51->72->86
FuncC(&start, 63, 51):48->51->63->72->86
再來看 diaplay ,第 4 行 for loop 的終止條件是`temp->next != start`也就是當跑到最後一個結點時就會跳出不會進去,所以在 for 的外面多 printf 一次就是印出最後一個值,當前結點在最後一個,
現在印出的:
`48 51 63 72 86`
第 8 行把結點改成最後一個,若這是一個環狀的 link-list 實際上註解掉好像也不會改變結果?
第 9 行邏輯和前面的一樣,把 for loop 終止在第一個結點,然後多 printf 一次,
所以最後會印出的順序是:
`48 51 63 72 86 86 72 63 51 48`
### 測驗 `2`
```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;
}
```
1. 終止條件是 `node && node != head` 當 node 跑到沒有宣告的區域,或是變成 head 時就會終止,可以想成跑一整個 link-list 在多一次 next 就跳出迴圈。
所以如果這不是環狀的 return node - head 就不會是 0 ,相反若是環狀的就會返回 0 。
而 `data++` 是 " 指向 data 的 address ++ " 而不是 data 裡面的值去++
所以這裡的答案不是 `(f)` 而是
`(e)` 判斷是否為 circular linked list,若為 circular 則回傳 `0`,其他非零值
```clike=
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;
}
```
2. 根據程式碼一個個排下來,現在的 link-list 是:
`0 -> 1 -> 2 -> 3 -> 4`
不是一個環狀的 link-list 所以第一個 `FuncX` 回傳非0值,所以
K1 是 YES
3. `head->next->next->next->next = head` 把最後一個 next 遮住不要看,就會發現其實是指 value 是 3 的結點,把 next 放進去一起看,就是把那個結點的 next 變成 head ,就變成了環狀 link-list
`0 -> 1 -> 2 -> 3 -> 0 ->........`
`FuncX` 回傳 0
所以 K2 是 NO
4. `head->next = head->next->next->next->next->next->next->next->next` 同樣用上一的看法,連到最後會發現連到 head 變成:
`0 -> 0 -> ......`
還是環狀的 所以 K3 是 NO
5. `head->next` 永遠是 head 所以整個結構沒有變
所以 K4 是 NO
6. head->data = 0
所以 K5 是 0
7. 這題第一題就提到過了,改變的只是在 function 的 local value 的 address 沒有真的改到 data
所以 是 0
## [第五週測驗題(上)](https://hackmd.io/s/SynmEVIqG)
### 測驗 `1`
```clike=
uint8_t Func32(uint32_t x) {
return x ? Func32(x >> 1) - 1 : 32;
}
```
1. 代數字進去是最快的解題方式,
把 0 帶入,會直接 return 32
把 1 帶入,進入一次遞迴, return 32 - 1
把 2 帶入,進入兩次遞迴, return ( 32 -1 ) -1
把 3 帶入,進入兩次遞迴, return ( 32 -1 ) -1
算到這裡就知道, `Func32` 會把傳進去的數字作邏輯右移,再 return ( 32 - 進入遞迴的次數 ),得到的結果就是
`(c)` 計算輸入數值 x 的二進位表示中,回傳開頭 (從高位到低位元) 有幾個 0,倘若 x 為 0x0,則輸出 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;
}
```
2. 知道這個 function 也是在算 clz 後理解就比較快了,5678行就是關鍵,分別用來確認 x 前 16 8 4 2 bits 是不是 0 如果是就加上去,並改變 x 的值,把要檢查的剩下部份位移到前面的幾個 bits。如果不是,就縮小範圍繼續檢查。
再來是 `n = n - (x >> 31)` 就是檢查剩下最後沒有被檢查到的那個 bit 。
在第六行時,要檢查的部份只剩下 x 的前 8 bits,所以 M1 = 24
以此類推,
第七行檢查前 4 bits ,所以M2 = 28
第八行檢查前 2 bits ,所以M3 = 30
知道這是 [afcidk](https://hackmd.io/s/SkS7PeXsf) 說的 二分搜尋法 就可以更快理解這個概念。
### 測驗 2
延伸測驗 1,將 Func32 應用於以下程式碼,可輸入給定 64-bit 數值的 10 進位表達方式 (類似 printf 函式的 %d 格式),補完程式碼並推測其作用。
Func64 的作用為?
```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;
}
```
## [第五週測驗題(中)](https://hackmd.io/s/HkQjgqI5G)
### 測驗 `1`
已知在 x86_64 架構,以下程式碼的執行輸出是 `jserv++C`:
```clike=
#include <stdio.h>
int main() {
puts((char *) &(double []){ 3823806048287157.0, 96 });
}
```
根據題目的意思,宣告一個 double 變數,然後在"不改變其記憶體內容下"強制轉成 char 並輸出,關鍵點就在於 double 變數裏面儲存的到底是什麼東西。
根據 [double converter](http://www.binaryconvert.com/convert_double.html#) 將
`3823806048287157.0` 轉換後會變成 `0x432B2B767265736A`
再把得到的十六進位數字丟到 [hex to string converter](https://codebeautify.org/hex-string-converter)
`0x432B2B767265736A` 轉換後會得到 `C++vresj`
發現跟實際得到的結果是完全相反的,這是因為 x86-64 架構是 little-endian 就是說當我們要存一個 double 數
`0x432B2B767265736A`
它實際上在記憶體的內容會是
`0x6A736572762B2B43`
所以用 char 一個 byte 一個 byte 去讀,結果就會是相反的
參考:[Big and Little Endian Byte Order](http://www.yolinux.com/TUTORIALS/Endian-Byte-Order.html)
考慮以下程式碼,假設 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; }
```
從這個遞迴式子可以看出,把 m[0] 乘上 $2^{96}$ 後再把它印出來,對 double 的作用剛好就是直接在 Exponent 加上 96,所以
把 `0x432B2B767265736A` 轉換成 binary 後會得到
```
s Exponent Mantissa
0 10000110010 1011001010110111011001110010011001010111001101101010
```
在 Exponent 加上 96,也就是 00001100000 :
```
0 10000110010 1011001010110111011001110010011001010111001101101010
+ 00001100000
------------------------------------------------------------------
0 10010010010 1011001010110111011001110010011001010111001101101010
```
再把他轉換成十六進位
`0x492B2B767265736A (big-endain)`
`0x6A736572762B2B49 (little-endain)`
最後再轉成 String 就會是
`jserv++I`
:::success
延伸題目:
1. 修改程式,允許輸出你的 GitHub 帳號名稱
2. 承上,修改 gen() 函式,透過特定的轉換,讓 `m[]` 的內容變為你的英文姓氏 (last name),程式碼應該越短越好,而且不得以字串的形式存在於原始程式碼
3. 如果在 big-endian 的環境中,要達到一樣的效果,我們該如何改寫?
:::
1. 我的 github 帳號是 BroLeaf 共7個 char ,轉換成 ascii code ,用 little-endian 表示的話就是如下,第一個 byte 是 NULL
`006661654C6F7242`
再轉成 double 然後把它印出來
```clike=
puts((char *) &(double []){9.95963171225462228399162147146E-307, 96});
//output:BroLeaf
```
2. 我姓葉,要輸出Yeh,為了要輸出更短的字串,我必須把Exponent 的 bit 清掉,也就是除 $2^6$ 。
但是在清掉的那一刻,就會從 denormalized 變成 normalized 會有計算偏移量的問題, Mantissa 就會跟著改變,所以只好除 $2^5$ ,雖然會導至多印出 'DEL' 這個 char 不過不影響實際上的結果。
為了使 Mantissa 符合需求,我在 puts 前面扣掉兩者的差 (diff)
```
BroLeaf :0 00000000110 0110011000010110010101001100011011110111001001000010
masked :0 00000000000 0110011000010110010101001100011011110111001001000010
Yeh :0 00000000000 0000000000000000000000000000011010000110010101011001
diff :0 00000000000 0110011000010110010101001100000001110000110011101001
```
```clike=
} else {
m[0] -= ( 8.87311048192124586227196764917E-309 ) ;
puts((char *) m);
return ;
}
//output:Yeh
```
3. 如果在 big-endian 的環境中,要達到一樣的效果,我們該如何改寫?
* 每 8 個 bytes 為一個單位,然後把bytes 按相反順序存入,例如要印出`BroLeaf`
little-endian 要存入 `faeLorB`
big-endian 則是要存入 `BroLeaf`