# 2018q3 Homework4 (assessment) ###### tags: `sysprog2018` Contributed by <[`aben20807`](https://github.com/aben20807)> ## 測驗 `1` ### 題目與解答 + 利用**二補數**特性完成無分支版本的 `abs()` (取絕對值) ```c #include <stdint.h> int64_t abs64(int64_t x) { int64_t y = x >> (64 - 1); return (x ^ y) - y; } ``` ### 想法 + 這題其實很直觀,因為**二補數**的提示相當明顯 + 首先用 `y` 來拿到 `x` 的 sign bit + 注意,因為 sign extension,所以 `y` 可能為 `0b000...0 (0)` 或 `0b111...1 (-1)` + 接著,若 `y` 為 `0` (`x` 正數),則 `(x ^ 0) - 0` 還是 `x` 本身 + 若 `y` 為 `-1` (`x` 負數),則 `(x ^ -1) - (-1)` 恰好是透過二補數將負轉正 ### 延伸 #### 1. overflow + 找出 `int64_t` 的最小值 ```c #include <limits.h> printf("%ld\n", LONG_MIN); // -9223372036854775808 ``` + 接著使用時發生怪異事件:compile warning ```bash warning: integer constant is so large that it is unsigned int64_t t = -9223372036854775808; ^~~~~~~~~~~~~~~~~~~ ``` + 原因是因為 `9223372036854775808` 已經超出範圍了,而 `-9223372036854775808` 其實是用 unary operator + 正確用法為 `-9223372036854775807 - 1`, `0x8000000000000000 ` 或直接使用 `LONG_MIN` + 參考資料:[Hard to C - INT_MIN](http://hardtoc.com/2009/07/16/int-min.html) + 與一開始想的情況一樣,最小值是沒辦法取絕對值 + 最小值取絕對值過程 `(0b1000...0 ^ (-1)) + 1 = 0b0111...1 + 1 = 0b1000...0` 又變回自己 #### 2. pseudo-random number generator (PRNG) + 關於 `0xdeadbeef` + 在 `dlist` 的作業中也有印象 [[link]](https://github.com/sysprog21/lab0-c/blob/master/harness.c#L19) + google 搜尋會推薦 `deadbeef-dead-beef-dead-beef deadbeef` 奇怪的字串,然後就出現 [wiki - Hexspeak](https://en.wikipedia.org/wiki/Hexspeak) + 比較神奇的在於只使用 `a` 到 `f` 的字母 + PRNG (http://xoshiro.di.unimi.it/) ```c static uint64_t r = 0xdeadbeef int64_t rand64() { r ^= r >> 12; r ^= r << 25; r ^= r >> 27; return (int64_t) (r * 2685821657736338717); } ``` + 關於程式碼,來源應該是來自 [Sebastiano Vigna](https://en.wikipedia.org/wiki/Sebastiano_Vigna) 的 paper: [An experimental exploration of Marsaglia’s xorshift generators, scrambled](http://vigna.di.unimi.it/ftp/papers/xorshift.pdf),在第 20 頁有提及 :::warning ### 問題 對於測試這點我還是無法聯想和 PRNG 有什麼關聯 1. 若要對整個 int64_t 數值範圍測試,那使用迴圈直接從最小跑到最大不是才能保證有測試完整個範圍 (雖然要跑超久)? 使用 PRNG 反而無法保證有完整測試 3. 如何偵錯? ```c for (int64_t i = LONG_MIN; i <= LONG_MAX; i++) { int64_t abs_i = (i < 0) ? -i : i; if (abs64(i) != abs_i) printf("error!\n"); } ``` 但是,`abs_i` 其實也是錯的,因為 `-1 * LONG_MIN` 就會發生 integer overflow ::: + 應用 + [**BitShares (BTS)**](https://zh.wikipedia.org/wiki/%E6%AF%94%E7%89%B9%E8%82%A1) - [db_witness_schedule](https://github.com/bitshares/bitshares-core/blob/master/libraries/chain/db_witness_schedule.cpp#L122-L126) + **ROS** - [Pseudo Random Number Generator Script (xorshift64* )](https://forum.mikrotik.com/viewtopic.php?t=100868) + 其他 + 竟然有歌 ... [Yoshino Yoshikawa - PRNG](https://www.youtube.com/watch?v=oOVINg5rH1s) ## 測驗 `2` ### 題目 + TOC + `test.h` 展開後 + [x] 1. TCO:可直接在 `first` 中展開 ```c void second_zero (void); void first_zero (void) { second_zero (); } void second_zero (void) { } ``` --- + [x] 2. TCO:可直接在 `first` 中展開 ```c void second_one (int a); void first_one (int a) { second_one (0); } void second_one (int a) { } ``` --- + [x] 3. TCO ```c void second_zero_one (int a); void first_zero_one (void) { second_zero_one (0); } void second_zero_one (int a) { } ``` --- + [x] 4. TCO ```c void second_one_zero (void); void first_one_zero (int a) { second_one_zero (); } void second_one_zero (void) { } ``` --- + [ ] 5. TCO ```c char second_ret_int_char (void); int first_ret_int_char (void) { return second_ret_int_char (); } char second_ret_int_char (void) { return 0; } ``` --- + [ ] 6. TCO ```c int second_ret_char_int (void); char first_ret_char_int (void) { return second_ret_char_int (); } int second_ret_char_int (void) { return 0; } ``` --- + [x] 7. TCO ```c int second_ret_void_int (void); void first_ret_void_int (void) { second_ret_void_int (); } int second_ret_void_int (void) { return 0; } ``` + 題目可以用 TOC ? [[link]](https://godbolt.org/z/TIEQqk) :::warning 問題:無法解釋以下現象 ::: ```c void g(int *p); void f(void) { int x = 3; g(&x); } void g(int *p) { printf("%d\n", *p); } ``` + `gcc 8.2 -O2` result: ```asm .LC0: .string "%d\n" f: movl $3, %esi movl $.LC0, %edi xorl %eax, %eax jmp printf g: movl (%rdi), %esi xorl %eax, %eax movl $.LC0, %edi jmp printf main: subq $8, %rsp movl $3, %esi movl $.LC0, %edi xorl %eax, %eax call printf xorl %eax, %eax addq $8, %rsp ret ``` + 在 `g()` 中對指標取值再操作也可以 [[link]](https://godbolt.org/z/Hm3PSK) ```c void g(int *p) { *p += 1; printf("%d\n", *p); } ``` + 在 `g()` 中印兩次 `*p` 才不行 [[link]](https://godbolt.org/z/L7Y45g) ```c void g(int *p) { printf("%d\n", *p); printf("%d\n", *p); } ``` + 或是印出 `__builtin_return_address(0)` 也不行 [[link]](https://godbolt.org/z/ob1hyA) ```c void f(void) { printf("%p\n", __builtin_return_address(0)); int x = 3; g(&x); } void g(int *p) { printf("%p\n", __builtin_return_address(0)); printf("%d\n", *p); } ``` + `__builtin_return_address(level)`: + `level` 為 `0` 回傳目前函式的位址,為 `1` 回傳 caller 函式的位址 + 利用回傳值是否相同即可判斷是否為同一個 stack frame + [Jserv's blog: GCC 函式追蹤功能](http://blog.linux.org.tw/~jserv/archives/001870.html) 中有介紹除了 `__builtin_return_address` 還有其他選擇 + 例如用 inline **建議**編譯器 inline + 輸出的位址不一樣,代表失敗 ```c #include <stdio.h> static inline void func(); int main() { printf("%p\n", __builtin_return_address(0)); // 0x7fa6ed31eb97 func(); return 0; } static inline void func() { printf("%p\n", __builtin_return_address(0)); // 0x559cf3ed2670 } ``` + 強制 gcc 做 inline + 輸出位址相同 ```c #include <stdio.h> static inline void func() __attribute__((always_inline)); int main() { printf("%p\n", __builtin_return_address(0)); // 0x7f7b4dbe4b97 func(); return 0; } static inline void func() { printf("%p\n", __builtin_return_address(0)); // 0x7f7b4dbe4b97 } ``` ### 延伸 + Android 原始程式碼中的 `__builtin_return_address` + google 搜尋 `__builtin_return_address site::https://android.googlesource.com/` ## 測驗 `3` ### 題目 ```c int main() { int *ptr = 0; return *ptr; } ``` + result: ```bash terminated by signal SIGSEGV (Address boundary error) ``` --- + `ptr1.c` ```c int main() { return *((int *) 0); } ``` + result: ```bash terminated by signal SIGSEGV (Address boundary error) ``` --- + `ptr2.c` ```c int main() { return &*((int *) 0); } ``` + result: ```bash warning: return makes integer from pointer without a cast [-Wint-conversion] int main() { return &*((int *) 0); } ^~~~~~~~~~~~~ ``` --- + `ptr3.c` ```c #include <stddef.h> int main() { return &*NULL; } ``` + result: ```bash warning: dereferencing ‘void *’ pointer int main() { return &*NULL; } ^ warning: return makes integer from pointer without a cast [-Wint-conversion] int main() { return &*NULL; } ^ ``` --- + `ptr4.c` ```c #include <stddef.h> int main() { return &*(*main - (ptrdiff_t) **main); } ``` + result: ```bash warning: return makes integer from pointer without a cast [-Wint-conversion] return &*(*main - (ptrdiff_t) **main); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``` ### 想法 + 首先確認 `NULL` 是什麼 + 規格書 [N1256](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf) 找到 §6.3.2.3 下方 (55) 提到 > The macro NULL is defined in <stddef.h> + 要怎麼找到這個 `stddef.h` 寫什麼呢,我先去 google 搜尋,但是有很多種版本,突然想到透過 `gcc -E` 來找,發現我的程式會去 include `/usr/lib/gcc/x86_64-linux-gnu/7/include/stddef.h`,接著找到內容有這一段,合理猜測 `NULL` 是定義成 `((void *)0)` ```c /* A null pointer constant. */ #if defined (_STDDEF_H) || defined (__need_NULL) #undef NULL /* in case <stdio.h> has defined it. */ #ifdef __GNUG__ #define NULL __null #else /* G++ */ #ifndef __cplusplus #define NULL ((void *)0) #else /* C++ */ #define NULL 0 #endif /* C++ */ #endif /* G++ */ #endif /* NULL not defined and <stddef.h> or need NULL. */ #undef __need_NULL ``` ## 因為自動飲料機而延畢的那一年