# 2018q3 Homework3 (review) contributed by <`krimson8`> ## 第二週測驗 q2 ### 解讀 ```clike= void (*signal(int sig, void (*handler)(int))) (int); ``` 根據之前自己整理出來的口訣 * `*f()` 是function returning a pointer * `(*f)()` 是pointer to a function 因此根據上面的程式碼,可以一步一步分析出來 * `void (*handler)(int)` * `handler` 是pointer to a function,此 function 有一個 `int` 的 parameter * 此 function 將 return `void` * `*signal()` * 是一個function,他將會 return 一個 pointer * `void (*signal()) (int)` * `*signal()` 回傳的 pointer 會指向另外一個 function,此 function 有一個 `int` 的 parameter * 此 function 將 return `void` [Stack Overflow](https://stackoverflow.com/questions/15739500/how-to-read-this-prototype)上的一篇回覆有更好的解釋 ### signal 的功用 這裏列出 man signal(7) 的 standard signal,有多個value 的signal 名字意味着在不同的架構下又不一樣的value,中間的多數實作於x86 與 arm,右邊的是 mips 用 | Signal | Value | Action| Comment | |-- | --| --| --| |SIGHUP | 1 | Term | Hangup detected on controlling terminalor death of controlling process| |SIGINT | 2 |Term |Interrupt from keyboard| |SIGQUIT | 3 |Core |Quit from keyboard| |SIGILL | 4 |Core |Illegal Instruction| |SIGABRT | 6 |Core |Abort signal from abort(3)| |SIGFPE | 8 |Core |Floating-point exception| |SIGKILL | 9 |Term |Kill signal| |SIGSEGV | 11 |Core |Invalid memory reference| |SIGPIPE | 13 |Term |Broken pipe: write to pipe with no readers; see pipe(7)| |SIGALRM | 14 |Term |Timer signal from alarm(2)| |SIGTERM | 15 |Term |Termination signal| |SIGUSR1 |30,10,16 |Term |User-defined signal 1| |SIGUSR2 |31,12,17 |Term |User-defined signal 2| |SIGCHLD |20,17,18 |Ign |Child stopped or terminated| |SIGCONT |19,18,25 |Cont |Continue if stopped| |SIGSTOP |17,19,23 |Stop |Stop process| |SIGTSTP |18,20,24 |Stop |Stop typed at terminal| |SIGTTIN |21,21,26 |Stop |Terminal input for background process| |SIGTTOU |22,22,27 |Stop |Terminal output for background process| 上表中我們較爲熟悉的是 `SIGINT` 和 `SIGSEGV`,前者是在linux 終端執行程式的時候按下 ctrl+c 會給出的訊號,後者是發生 segmentation fault 系統給出的訊號 這裏附上一個範例程式,說明signal 的用法,執行的時候可以使用 ctrl+z 來退出程式 [來源](https://www.thegeekstuff.com/2012/03/catch-signals-sample-c-code/) ```clike= #include <stdio.h> #include <signal.h> #include <unistd.h> void sig_handler(int signo) { if (signo == SIGINT) printf("received SIGINT\n"); } int main(void) { if (signal(SIGINT, sig_handler) == SIG_ERR) printf("\ncan't catch SIGINT\n"); // A long long wait so that we can easily issue a signal to this process while(1) sleep(1); return 0; } ``` 這個程式碼示範了如何使用 `signal()` 建立 signal handler 根據 man signal `sighandler_t signal(int signum, sighandler_t handler);` :::success signal() **sets the disposition(部署) of the signal signum to handler**, which is either **SIG_IGN, SIG_DFL**, or the address of a **programmer-defined function (a "signal handler")**. If the signal signum is delivered to the process, then one of the following happens: * If the disposition is set to **SIG_IGN, then the signal is ignored**. * If the disposition is set to **SIG_DFL, then the default action** associated with the signal (see signal(7)) occurs. * If the disposition is set to a function, then first either the disposition is reset to SIG_DFL, or the **signal is blocked (see Portability below), and then handler is called with argument signum**. If invocation of the handler caused the signal to be blocked, then the signal is unblocked upon return from the handler. The signals **SIGKILL and SIGSTOP cannot be caught or ignored**. **signal() returns** the **previous value of the signal handler**, or **SIG_ERR on error**. In the event of an error, errno is set to indicate the cause. ::: 因此程式碼首先將 `SIGINT` (ctrl+c)部署給 自行定義的 `sig_handler()`,`sig_handler` 內檢測收到的 signal 是不是 `SIGINT`,若是的話則執行程式碼。我們也可以設定多種signal handler 給不同的signal 使用,像是 ctrl+z 會觸發的 `SIGTSTP`。 根據man page 的解釋,`sighandler_t` 這個 type 的使用是一種 GNU 的extension,而題目所問的 `void ( *signal(int signum, void (*handler)(int)) ) (int);` 則是沒有使用上述的type 的時候 signal 的定義方法。 ## 第二週測驗 q3 `for (pos = (head)->prev; pos != (head); pos = pos->prev)` 每一次 `head` 的使用都要先加上()的原因 根據 [C Operator Precedence](https://en.cppreference.com/w/c/language/operator_precedence) "`*`"(dereference) 的 precedence level 是要低於 "`->`"(member access)的,因此若有需要 derefer `head` 的時候,若不加上括號,程式碼的行爲就會和預想中的不一樣 例子:與`*head->next` 相等的表達式 `*(head->prev)` 尋找 [linux/list.h](https://github.com/torvalds/linux/blob/6f0d349d922ba44e4348a17a78ea51b7135965b1/tools/include/linux/list.h) 內相似的程式碼實作 ```clike= #define list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); pos = pos->next) ``` 與 `list_for_each_prev` 的原理差不多,從 `head->next` 開始進行 list 的尋訪 ```clike= #define list_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; pos != (head); \ pos = n, n = pos->next) ``` 這裏 safe 的意思是 safe against removal of list entry,因爲使用多一個 list 結構 `n`作爲暫存點,所以可以在程式碼內進行 `pos` 點的removal,remove 完了 list 的探訪還能繼續下去 ## 第一週測驗 q1 ```clike= int my_xor(int x, int y) { return (x | y) OP1 (OP2 x OP3 OP4 y); } int main() { int x = 3, y = 5; return my_xor(x, y); } ``` 把OP 都補上,完整程式碼- ```clike= int my_xor(int x, int y) { return (x | y) & (~x | ~y); } int main() { int x = 3, y = 5; return my_xor(x, y); } ``` 這一題的概念在大二上的數位電路課有教過,推理的原理如下 * 首先我們有 `x | y`,表示說x 的位元和 y 的位元其一爲一即可,那麼會有一下幾種情況 * x | y 的結果是 0 * 這邊就可以推敲出 OP1 是 `&` 因爲在這種情況下,`x ^ y` 的結果一定是0,若不看後面的結果的話那就用 `&` * x | y 的結果是 1,那麼又有下面兩個情況 * x 和 y 皆是 1,則後面的判斷式必須回傳 0 * x 和 y 分別是0 和1,則需要確保後面的判斷式也回傳1 若要後面的判斷式回傳1,,且 OP2 和 OP4 都必須填上的情況(OP2 和 OP4 皆爲 `~`)則確定了 OP3 是 `|`,這樣也能滿足 x 和 y 皆是1 的情況 翻閱以前的學到的公式,xor 也可透過一下的方式呈現 * `return (x & ~y) | (~x & y)` * `return (x | y) & ~(x & y)` ## 第一週測驗 q2 這一題當天在做的時候其實用了很取巧的方式去想這個問題,並沒有深入理解程式碼實作的內容 當天的想法: ```clike= int max = 16; while (max >= 8) { num = num ^ (num >> max); max /= N; } ``` 看到這邊很自然的就覺得這個程式碼不會只跑一次,因此2,4,8 這三個選項 裏面只有 2 有可能 分析了程式碼和瞭解了 parity bit 的做法過後,嘗試解釋這份程式碼 ```clike= #include <stdio.h> /* Generate look-up table while pre-processing */ #define P2(n) n, n ^ 1, n ^ 1, n #define P4(n) P2(n), P2(n ^ 1), P2(n ^ 1), P2(n) #define P6(n) P4(n), P4(n ^ 1), P4(n ^ 1), P4(n) #define LOOK_UP P6(0), P6(1), P6(1), P6(0) /* LOOK_UP is the macro expansion to generate table */ unsigned int table[256] = { LOOK_UP }; int parity(int num) { /* Number is considered to be of 32 bits */ int max = 16; // Dividing the number into 8-bit // chunks while performing XOR while (max >= 8) { num = num ^ (num >> max); max /= N; } // Masking the number with 0xff (11111111) // to produce valid 8-bit result return table[num & 0xff]; } int main() { unsigned int num = 1742346774; /* Result is 1 for odd parity, 0 for even parity */ int result = parity(num); printf("%s parity\n", parity(num) ? "odd" : "even"); return 0; } ``` * 首先我們看到了程式碼一開始建立了一個 大小爲 256 的 `table[]`,爲什麼是256 呢? 因爲在`int parity()` 裏面 return 了一個值,值是 `table` 裏的某一個元素,而該index 與 0xff mask,意味着是一個8bit 的數字,而 2 的 8次方就是 256 * 那麼他是怎麼判斷parity bit 的呢?原理如下: * 把 32-bits 的int 分割成四個等分,從左到右我們標記爲 p1, p2, p3, p4 * 把 p1 和 p2 做 xor,p3 和 p4 做 xor ,經過這一步所出來的結果 * 1 的話,代表 0 和 1 的存在,需要繼續進行下一步判斷 * 0 的話,代表 0 和 0 或 1 和 1 然後吧兩組結果標記爲 t1 和 t2 繼續進行下一步 * 讓 t1 和 t2 進行 xor,經過這一步所出來的結果 * 1 的話,代表 0 和 1 的存在,也就是說p1, p2, p3, p4中,出現了奇數的1 * 0 的話,代表 0 和 0 或 1 和 1 , 0 和 0 的話可能是p1, p2, p3, p4中有 0 個 1 或者4 個 1, 1 和 1 的話則是剛好 p1, p2, p3, p4 中有 2 個 1 * 再來我們看看 `table[256]` 張開的結果 ``` 0 1 1 0 1 0 0 1 1 0 0 1 0 1 1 0 1 0 0 1 0 1 1 0 0 1 1 0 1 0 0 1 1 0 0 1 0 1 1 0 0 1 1 0 1 0 0 1 0 1 1 0 1 0 0 1 1 0 0 1 0 1 1 0 1 0 0 1 0 1 1 0 0 1 1 0 1 0 0 1 0 1 1 0 1 0 0 1 1 0 0 1 0 1 1 0 0 1 1 0 1 0 0 1 1 0 0 1 0 1 1 0 1 0 0 1 0 1 1 0 0 1 1 0 1 0 0 1 1 0 0 1 0 1 1 0 0 1 1 0 1 0 0 1 0 1 1 0 1 0 0 1 1 0 0 1 0 1 1 0 0 1 1 0 1 0 0 1 1 0 0 1 0 1 1 0 1 0 0 1 0 1 1 0 0 1 1 0 1 0 0 1 0 1 1 0 1 0 0 1 1 0 0 1 0 1 1 0 1 0 0 1 0 1 1 0 0 1 1 0 1 0 0 1 1 0 0 1 0 1 1 0 0 1 1 0 1 0 0 1 0 1 1 0 1 0 0 1 1 0 0 1 0 1 1 0 ``` 就是2 進制下,8-bit 的 `00000000` ,不斷+1 的parity bit 首四個元素 0 1 1 0 對應如下 * 00000000 -> 0 * 00000001 -> 1 * 00000010 -> 1 * 00000011 -> 0 從 0 1 1 0 開始,我們可以看到有以下的規律,以四個元素爲單位,不是 1001 就是 0110,並且根據2進位下不斷的+1 所形成。因此 ```clike= #define P2(n) n, n ^ 1, n ^ 1, n #define P4(n) P2(n), P2(n ^ 1), P2(n ^ 1), P2(n) #define P6(n) P4(n), P4(n ^ 1), P4(n ^ 1), P4(n) #define LOOK_UP P6(0), P6(1), P6(1), P6(0) ``` 就是對應上述的規律所產生的 ### CRC 程式碼 ## 第三週測驗 q2 原題程式碼 ```clike= int main() { union { int a; char b; } c = { .a = K1 }; return c.b == K2; } ``` K1 和 K2 都是 1,可分爲以下兩種情況 * little endian 假設4 byte 的地址編號如下,則 `int a` 在系統內的存放方式如下 |Address|0x0A|0x0B|0x0C|0x0D| |-- |-- |-- |-- |-- | |Value |0x01|0x00|0x00|0x00| 因此存取 b 的時候,b 和 a 共享的空間是 0x0A,因此會return 1 * big endian `int a` 在系統內的存放方式如下 |Address|0x0A|0x0B|0x0C|0x0D| |-- |-- |-- |-- |-- | |Value |0x00|0x00|0x00|0x01| 因此存取 b 的時候,b 和 a 共享的空間是 0x0A,因此會return 0 ### 類似的判斷big endian 或是 little endian 的程式碼 [來源](https://stackoverflow.com/questions/12791864/c-program-to-check-little-vs-big-endian#) ```clike= #include <inttypes.h> int main(int argc, char ** argv){ volatile uint32_t i=0x01234567; // return 0 for big endian, 1 for little endian. return (*((uint8_t*)(&i))) == 0x67; } ``` 這段程式碼若運行在 little endian 的機器上 輸出結果爲 1,反之則爲 0 i 在 little endian 系統內的存放 |Address|0x0A|0x0B|0x0C|0x0D| |-- |-- |-- |-- |-- | |Value |0x67|0x45|0x23|0x01| 因此若把他 cast 成只有 8-bits 的integer,將會只剩下 0x0A 的內容