2018q1 Homework3 D06:c-review === contributed by<`blake11235`> --- ## 第 4 週測驗題 ### 測驗`1` ![](https://i.imgur.com/iqFZJkY.png) 這提主要是在考 link list 的觀念,直接來看程式碼 * FuncA ```=c 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; } ``` 若一開始的 `*start` 是空的,便會 malloc 一個新的 node 並把 value 輸入,next 和 prev 都指向自己,最後把自己的位置丟給 `*start` 若一開始已經有存在的 node,會創一個新的`new_node`並把他聯結在`start`的前一個位置,把 new_node 的 next 指向 start,prev 指向 last 所以答案`是建立新節點,內容是 value,並安插在結尾` * FuncB ```=c 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; } ``` 也是一樣建立一個 new_node,將其按插在目前 start 的前一個位置,但最後將 start 指向這個 new_node 所以答案是`建立新節點,內容是 value,並安插在開頭` * FucnC ```=c 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; } ``` 建立一個 new_node,其值為 value1,然後依序從 start 開始搜尋值為 value2 的 node,找到了之後將他按插在 temp 的後面 所以答案是`找到節點內容為 value2 的節點,並在之後插入新節點,內容為 value1` * 程式輸出 * 首先是建立資料 ```=c 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; } ``` 48 -> 51 -> 63 -> 72 -> 86 -> start 指向 48 * 再來是印出數字 ```=c 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"); } ``` `Traversal in forward direction` 從 start 開始直到最後一個的下一個又是 start,所以答案是 **48、51、63、72、86** `Traversal in reverse direction` 倒著輸入,起始是 start 的 prev,所以答案是 **86、72、63、51、48** --- ### 測驗`2` ![image](https://i.imgur.com/KKSlhnO.png) * FuncX() ```=c 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 迴圈裏面看到, node 從 head 的下一個開始,然後一直往 next 移動,直到 node 為 NULL 或者 node 回到 head 最後 `return node - head` 使用來判斷是否為 circle link list,如果是的話回傳值會為 0 所以答案是`判斷是否為 circular linked list,若為 circular 則回傳 0,其他非零值` * K1 ```=c 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"); ``` node_new() 這個函會建立一個結點,並且將其 next 指向 NULL 上述程式會得到 0 > 1 > 2 > 3 > 4 > NULL 所以 FuncX() 會回傳**非零值**,因為此 link list 並非 circle ~我就是死在這裡~ 因此會 print 出 "Yes" * K2 ```=c head->next->next->next->next = head; printf("K2 >> %s\n", FuncX(head, &count) ? "Yes" : "No"); ``` 這個段程式將原本第 4 個 node->next 所指向的 第 5 個 node 改成指向 head 0 > 1 > 2 > 3 > 0 第 5 個 node 4 也就被 leak 此為 circle link list,所以 print 出 "No" * K3 ```=c head->next->next->next->next->next = head->next; printf("K3 >> %s\n", FuncX(head, &count) ? "Yes" : "No"); ``` 此處的 link list 沒有變,還是一樣 所以 print 出 "No" * K4 ```=c head->next = head->next->next->next->next->next->next->next->next; printf("K4 >> %s\n", FuncX(head, &count) ? "Yes" : "No"); ``` 第一個 node 的 next 指到第一個 node,所以變成 0 > 0 ,還是 circle 所以 print 出 "No" * K5 ```=c printf("K5 >> %d\n", head->next->data); ``` 第 1 個結點的值是 0 * Count 根據 FuncX() 裏面有一個 `data++` 照理來說應該每走訪一個點就要加一,但仔細觀察 FuncX() ```=c int FuncX(struct node *head, int *data) { struct node *node; for (node = head->next; node && node != head; node = node->next) data++; return node - head; } ``` 可以發現宣告的 `int *data` 是位址,使用的是 pass by address,但後面卻對位置做 ++ ,所以功用沒有達到。若直接去 compile 會得到 count = 0 跟一開始宣告的一樣 若要更改應該改成 ```=c - data++; + *data++; ``` --- ## 第 5 週測驗題(上) ### 測驗`1` * 原程式 ```=c uint8_t Func32(uint32_t x) { return x ? Func32(x >> 1) - 1 : 32; } ``` 首先是一個輸入的 32 位元 x,先看極端例子,若 x = 0,可接得到答案 32 ,從這個角度去設想題目應該是要判斷 x 總共有幾個 0,結果是錯的哈哈 程式會一直進行向右移 1 bite 直到全部為 0 ,得到 32 之後一直返回 -1 ,可以得到從右邊數來的最後一個 1 是第幾個 所以答案是 `計算輸入數值 x 的二進位表示中,回傳開頭 (從高位到低位元) 有幾個 0,倘若 x 為 0x0,則輸出 32` * 等價程式 ```=c 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; } ``` 可以理解第 1 行,若 x = 0,直接回傳 32 若 x 向右移動 16 ,也就是 x 的左半部份,全是 0 的話,n 加 16,x 左移 16 繼續判斷右半部份 接下來要 x 向右移動 16+8 ,判斷最高的前 8 位是否為 0 ,若是的話左移 8 使傷胃判斷的部份移動到最高位元 再來也是一樣右移 16+8+4,最後是位移 16+8+4+2 可以得出 M1 = 24 , M2 = 28 , M3 = 30 --- ### 測驗`2` * Func64 ```=c 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); } ``` 先將 uint64_t 拆成各 32 bits 的 lo, hi ,然後個別做 Func32() 完成其開頭有幾個 0 ,若 hi 不為 0 ,直接輸出 Func(hi) ,為 0 的話,需要對 Func(lo) 多加上前 32 個 0,所以答案是 `計算輸入數值 x 的二進位表示中,回傳開頭 (從高位到低位元) 有幾個 0,倘若 x 為 0x0,則輸出 64` :::info TODO: inline http://gnitnaw.github.io/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/2016/06/14/C_inline.html ::: * do_udiv32 ```=c 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; } ``` 可以判斷出這項函式是在處理 32 位元的除法 這除法的概念: ```flow st=>start: 確認 divisor <= dividend e=>end: 剩餘的 dividend 輸入 res 內的餘數 op1=>operation: 將 divisor & dividend 對齊 op2=>operation: 將 mask 設成跟 divisor 的最高位一樣 op3=>operation: dividend - divisor op4=>operation: mask 輸入商數 q op5=>operation: divisor & mask 右移 1 cond=>condition: 是否可減? cond2=>condition: 被除數除完了或除數為零了 st->op1->op2->cond->op5->cond2 cond(yes)->op3->op4 cond(no)->op5 cond2(yes)->e cond2(no)->cond ``` 可以知道 P1 是 mask>>=1 * udiv64 ```=c 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; } ``` 這斷程式只是把 udiv32() 改成 64 位元版本,所以概念一樣 差別在於多了處理 `divisor 為 0`、`divisor = dividend`、`divisor > dividend` 情況 並且將位元數小於 32 的情況給 Func32() 處理 答案的 bits-- 跟上述的 mask 有著一樣的概念,只不過這裡分開來表示而已 * main ```=c 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; } ``` :::warning complex ::: --- ## 第 5 週測驗題(中) ```=c #include <stdio.h> int main() { puts((char *) &(double []){ 3823806048287157.0, 96 }); } ``` x86_64 架構之下,可以印出 `jserv++C` 分析以下程式 ```=c #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 的數值 3823806048287157.0 用二進位表示的話是 `01000011 00101011 00101011 01110110 01110010 01100101 01110011 01101010` 程式是將 double 的型式強迫轉換成 char 分別以 4 位元一個個輸出,又加上電腦是以 little-endian 輸出,所以看起來是 `C++vresj` 輸出會是 `jservC++` 透過 IEEE-754 的表示法 | s | exp | frac | |:--:|:--:|:--:| |0| 1000011 0010 | 1011 00101011 01110110 01110010 01100101 01110011 01101010 | |0| 1001001 0010 | 1011 00101011 01110110 01110010 01100101 01110011 01101010 | gen() 會使 m[0] 一直乘 2 乘 96 次,也就是在階碼的部份 + 96 1001001 0010 + 96 = 1001111 001 01001001 001 透過 ASCII 可得到 `I` ### 好玩的延伸 輸出你的 GitHub 帳號名稱 |字元|ASCII 2進位|16 進位| |:--:|:--:|:--:| |b|0110 0010|62| |l|0110 1100|6C| |a|0110 0001|61| |k|0110 1011|6B| |e|0110 0101|65| |1|0011 0001|31| |1|0011 0001|31| |2|0011 0010|32| |3|0011 0011|33| |5|0011 0101|35| 我的 ID 超過了 8 個字元,所以一個 double 不夠用,那剩下的就在叫一個 double 所以就變成 `211ekalb` 和 `53` ```=c #include <stdio.h> int main(){ printf("%s%s", (char *) (double []){6.37722099392140580485191278765E-67}, (char *) (double []) {6.72868003071193668514069039007E-320} ); return 0; } ``` 但是...卻產生了一坨奇怪的符號 ``` blake112�@35 ``` 可是個別表示都沒有問題 ```=c #include <stdio.h> int main(){ printf("%s", (char *) (double []) {6.37722099392140580485191278765E-67} //, (char *) (double []) {6.72868003071193668514069039007E-320} ); return 0; } blake112 ``` ```=c #include <stdio.h> int main(){ printf("%s", /*(char *) (double []) {6.37722099392140580485191278765E-67}, */ (char *) (double []) {6.72868003071193668514069039007E-320} ); return 0; } 35 ``` 正當我在懷疑人生的時候,突然!! ```=c #include <stdio.h> int main(){ printf("%s%s", (char *) (double []) {6.37722099392140580485191278765E-67, 0}, (char *) (double []) {6.72868003071193668514069039007E-320, 0} ); return 0; } ``` 大家來找碴 ```=c - {......} + {......, 0} ``` **簡單來說,如果就是陣列宣告的問題** 我將程式改成依序印出 1 byte 與其位置的型式,從兩個陣列的開頭印出20個 ```=c #include <stdio.h> int main(){ double x[] = {6.37722099392140580485191278765E-67}; double y[] = {6.72868003071193668514069039007E-320}; char* a = x; char* b = y; for(int i=0; i<20; i++){ printf("%p %c\n", a+i, *(a+i)); } printf("\n"); for(int i=0; i<20; i++){ printf("%p %c\n", b+i, *(b+i)); } printf("%s", a); printf("%s", b); return 0; } ``` ``` 0x7ffdb6cc4620 b 0x7ffdb6cc4621 l 0x7ffdb6cc4622 a 0x7ffdb6cc4623 k 0x7ffdb6cc4624 e 0x7ffdb6cc4625 1 0x7ffdb6cc4626 1 0x7ffdb6cc4627 2 0x7ffdb6cc4628 � 0x7ffdb6cc4629  0x7ffdb6cc462a @ 0x7ffdb6cc462b 0x7ffdb6cc462c 0x7ffdb6cc462d 0x7ffdb6cc462e 0x7ffdb6cc462f 0x7ffdb6cc4630 3 0x7ffdb6cc4631 5 0x7ffdb6cc4632 0x7ffdb6cc4633 0x7ffdb6cc4630 3 0x7ffdb6cc4631 5 0x7ffdb6cc4632 0x7ffdb6cc4633 0x7ffdb6cc4634 0x7ffdb6cc4635 0x7ffdb6cc4636 0x7ffdb6cc4637 0x7ffdb6cc4638 0x7ffdb6cc4639 � 0x7ffdb6cc463a � 0x7ffdb6cc463b  0x7ffdb6cc463c � 0x7ffdb6cc463d _ 0x7ffdb6cc463e � 0x7ffdb6cc463f � 0x7ffdb6cc4640 0x7ffdb6cc4641 0x7ffdb6cc4642 @ 0x7ffdb6cc4643 blake112�@35 ``` 可以發現那個奇怪的符號出現在 `blake112` 後面,由於 printf("%s") 讀取字串的時候必須要有 `\0` 也就是 `0000 0000` 當作字串終止,因此會直接當作字元印出來。 而`35` 後面,我在賦值的時候就給定 0 了,所以不會出現其他字元。 有數種解決方式,但有著奇怪的現象: * 將陣列都給定第二個值,`double x[2]`,x[0] 為數值,x[1] 會自動為 0 用來當作 NULL 字串的終止。 但照理來說,需要在 `blake112` 後面放 `x[1] = 0 `,`35` 後面的 y[1] 可以不用放,但是... ``` double [] x = {} double [2] y = {} 0x7ffc45647700 b 0x7ffc45647701 l 0x7ffc45647702 a 0x7ffc45647703 k 0x7ffc45647704 e 0x7ffc45647705 1 0x7ffc45647706 1 0x7ffc45647707 2 0x7ffc45647708 0x7ffc45647709 0x7ffc4564770a 0x7ffc4564770b 0x7ffc4564770c 0x7ffc4564770d 0x7ffc4564770e 0x7ffc4564770f 0x7ffc45647710 3 0x7ffc45647711 5 0x7ffc45647712 0x7ffc45647713 0x7ffc45647710 3 0x7ffc45647711 5 0x7ffc45647712 0x7ffc45647713 0x7ffc45647714 0x7ffc45647715 0x7ffc45647716 0x7ffc45647717 0x7ffc45647718 0x7ffc45647719 0x7ffc4564771a 0x7ffc4564771b 0x7ffc4564771c 0x7ffc4564771d 0x7ffc4564771e 0x7ffc4564771f 0x7ffc45647720  0x7ffc45647721 x 0x7ffc45647722 d 0x7ffc45647723 E ``` 代表在 y 陣列的後面放入 0 也可以使 x[0] 後面變成 0 ?? * 先宣告 y 陣列在宣告 x 陣列 由於**先宣告的陣列後面會出現奇怪符號**,所以讓有 NULL 的 y 陣列先宣告 ``` double y[] = {}; double x[] = {}; 0x7ffd9ff1ea80 b 0x7ffd9ff1ea81 l 0x7ffd9ff1ea82 a 0x7ffd9ff1ea83 k 0x7ffd9ff1ea84 e 0x7ffd9ff1ea85 1 0x7ffd9ff1ea86 1 0x7ffd9ff1ea87 2 0x7ffd9ff1ea88 0x7ffd9ff1ea89 � 0x7ffd9ff1ea8a 0x7ffd9ff1ea8b  0x7ffd9ff1ea8c  0x7ffd9ff1ea8d � 0x7ffd9ff1ea8e y 0x7ffd9ff1ea8f � 0x7ffd9ff1ea90 0x7ffd9ff1ea91 0x7ffd9ff1ea92 @ 0x7ffd9ff1ea93 0x7ffd9ff1ea70 3 0x7ffd9ff1ea71 5 0x7ffd9ff1ea72 0x7ffd9ff1ea73 0x7ffd9ff1ea74 0x7ffd9ff1ea75 0x7ffd9ff1ea76 0x7ffd9ff1ea77 0x7ffd9ff1ea78 � 0x7ffd9ff1ea79  0x7ffd9ff1ea7a @ 0x7ffd9ff1ea7b 0x7ffd9ff1ea7c 0x7ffd9ff1ea7d 0x7ffd9ff1ea7e 0x7ffd9ff1ea7f 0x7ffd9ff1ea80 b 0x7ffd9ff1ea81 l 0x7ffd9ff1ea82 a 0x7ffd9ff1ea83 k ``` 果真先宣告的 y 陣列後面出現奇怪符號,但不會對整個字串輸出造成影響,但是... ``` double test[] = {0}; double x[] = {}; double y[] = {}; 0x7ffed9457250 b 0x7ffed9457251 l 0x7ffed9457252 a 0x7ffed9457253 k 0x7ffed9457254 e 0x7ffed9457255 1 0x7ffed9457256 1 0x7ffed9457257 2 0x7ffed9457258 � 0x7ffed9457259  0x7ffed945725a @ 0x7ffed945725b 0x7ffed945725c 0x7ffed945725d 0x7ffed945725e 0x7ffed945725f 0x7ffed9457260 3 0x7ffed9457261 5 0x7ffed9457262 0x7ffed9457263 0x7ffed9457260 3 0x7ffed9457261 5 0x7ffed9457262 0x7ffed9457263 0x7ffed9457264 0x7ffed9457265 0x7ffed9457266 0x7ffed9457267 0x7ffed9457268 0x7ffed9457269 ` 0x7ffed945726a A 0x7ffed945726b 0x7ffed945726c e 0x7ffed945726d � 0x7ffed945726e � 0x7ffed945726f � 0x7ffed9457270  0x7ffed9457271 0x7ffed9457272 @ 0x7ffed9457273 ``` 看來不只是第一個宣告的陣列後面會出現怪東西,第二個也會,大概可以推測連續宣告陣列與陣列之間存在某些未知數值... 但... ```=c #include <stdio.h> int main(){ double z[] = {5.25663347308138420229098632996E83 };//QQQQQQQQ double x[] = {6.37722099392140580485191278765E-67}; double y[] = {6.72868003071193668514069039007E-320}; char* c = z; char* a = x; char* b = y; for(int i=0; i<20; i++){ printf("%p %c\n", c+i, *(c+i)); } printf("\n"); for(int i=0; i<20; i++){ printf("%p %c\n", a+i, *(a+i)); } printf("\n"); for(int i=0; i<20; i++){ printf("%p %c\n", b+i, *(b+i)); } printf("%s", a); printf("%s", b); return 0; } ``` 我測出來,第一個陣列後面卻沒有出現任何奇怪符號,怎麼第一個又沒奇怪符號了 目前我給自己的解釋,在一區域內連續宣告不同名稱的陣列,之間可能間隔某些記憶體,其值可能有某種特殊規律。 ### gen() 修改 我要將`jservC++` 改成 `Tseng` |C|+|+|v|r|e|s|j| |--|--|--|--|--|--|--|--| |||\0|g|n|e|s|T| 在 IEEE-754 當中 |值| s | exp | frac | |:--:|:--:|:--:|:--:| |C++vresj|0| 1000011 0010 | 1011 00101011 01110110 01110010 01100101 01110011 01101010 | |gnesT|0| 1000011 0010 | 1011 00000000 01100111 01101110 01100101 01110011 01010100 | 有更動的只有 frac 部份 0100001100101011001010110111011001110010011001010111001101101010 3.823806048287157E15 0100001100101011000000000110011101101110011001010111001101010100 3.80013430248081E15 3.823806048287157E15 - 3.80013430248081E15 = 2.3671745806347 ```=c #include <stdio.h> double m[] = {3823806048287157.0, 96 }; void gen() { m[0] -= 23671745806347; puts((char *) m); } int main() { gen(); return 0; } ``` 可以成功輸出 `Tseng` --- ## 第 5 週測驗題(下) :::info QQ :::