--- title: WTF C description: 紀錄一些很怪的 C code 寫法 tags: C lang: zh_tw --- # WTF C [TOC] ## WTF1 [source](https://hackmd.io/@sysprog/c-control-flow?type=view#Duff%E2%80%99s-Device) ```c= #include <stdio.h> void dsend(int count) { if (!count) return; int n = (count + 7) / 8; switch (count % 8) { case 0: do { puts("case 0"); case 7: puts("case 7"); case 6: puts("case 6"); case 5: puts("case 5"); case 4: puts("case 4"); case 3: puts("case 3"); case 2: puts("case 2"); case 1: puts("case 1"); } while (--n > 0); } } int main() { dsend(15); } ``` output: ``` case 7 case 6 case 5 case 4 case 3 case 2 case 1 case 0 case 7 case 6 case 5 case 4 case 3 case 2 case 1 ``` ## WTF2 ```c= #include <stdio.h> int main(int argc, char *argv[]) { int a = 8; switch(argc % 2) a = 7; printf("%d\n", a); } ``` 可編譯可執行, `a = 7` 永遠不會執行到 參考 [duffexpln](http://c-faq.com/misc/duffexpln.html) > Rather, the syntax is simply > ``` > switch ( expression ) > statement > ``` > ## WTF3 ```c void f (); int main(void) { f(2, 3, 5487); return 0; } void f(int a) { int printf (); printf("%d %d\n", a); return; } ``` 這份 code 測試於 x64 的環境中 單純以 `gcc test.c -o test` 編譯 gcc 版本: `gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)` 其中包含了幾個問題點 - 宣告 `printf` 取代 `#include <stdio.h>` 這一點呢是關於 library 的問題 實際上 printf 的實作是在 `libc.so.6` (glibc), 而這個函數庫是 gcc 內建就會連結的 若像是 `sin` 這類 math 的函數, 其實作是在 libm, 要引用這個函數庫就要給 gcc `-lm` 參數 - `printf` 宣告於區域中 這一點是關於 C 標準的問題 函數聲明是能夠寫在區域中的, 但不行實作, C 語言不允許嵌套定義函數 - `f` 的宣告和定義兩者的參數量不同 這一點也是關於 C 標準的問題 實際上, 函數定義中沒有任何東西 (即 `()`), 這是 old style 在 C89 之後的標準, 若要沒任何東西, 需要寫 `(void)` compiler 遇到 old style 時, 不檢查宣告跟定義之間參數量跟類型是否相同 但若是寫 `(void)` 就會檢查 - 呼叫 `f` 時傳入的參數量不同 跟上一點一樣是 C 標準問題, 也跟 old style 有關 同樣的, compiler 遇到 old style 時, 不檢查定義與呼叫之間參數量跟類型是否相同 - 呼叫 `printf` 時的參數量和 format string 中需要的參數量不同 這一點是跟 prtinf 實作有關的問題 printf 的宣告是 `int printf (const char *__restrict __format, ...);` 實際上參數量不同, 編譯器還是會照常編譯成功 只是有可能出現 run-time error 而已 - 實際的輸出是什麼 `2 5487` 理解這個問題需要了解 x64 calling convention 簡單來說就是底層到底如何實現所謂的**呼叫函數**這件事情 呼叫函數包含了幾個問題, 其中有一點是如何傳遞參數 x64 傳遞參數的方式是將參數依序放到暫存器中(前三個參數會依序放到 rdi, rsi, rdx 這三個暫存器) 在 `f(2, 3, 5487)`, 相關暫存器會存放以下值: - rdi: 2 - rsi: 3 - rdx: 5487 而 `f` 呼叫的 `printf("%d %d\n", a)` 只有兩個參數, 所以只改動了以下: - rdi: 指向 `"%d %d\n"` 字串的指標 - rsi: 2 而 rdx 沒有被改, 所以還是 5487 那麼就好像呼叫了 `printf("%d %d\n", a, 5487)` ## WTF4 ```c int f (int x) { if (x > 0) return 1; else if (x < 0) return -1; } int main () { int a = f(0); printf("%d\n", a); } ``` 這份 code 測試於 x64 的環境中 單純以 `gcc test.c -o test` 編譯 gcc 版本: `gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)` 其中包含了幾個問題點 - `f` 在 `x == 0` 時沒有 return 這個狀況在 C standard 中是沒有定義的, 也就是說每個 compiler 對於這樣的狀況都有可能作不同的反應 而 gcc 是可以編譯, 但 return 的值無法得知 - `printf` 沒有定義 而最後還是能正確執行 `printf`, 原因是 compiler 自動加上了宣告, 之後的 linker 預設就有 link glibc, glibc 中含有 `printf` 的實作, 所以能成功鏈結出執行檔 ## WTF5 ```c struct s { int a; int b; int c; char str[10]; }; struct s f (struct s y) { struct s x = {1, 2, 3, "abcd"}; y.str[0] = 'e'; y.str[1] = 'd'; y.str[2] = 'i'; y.str[3] = 't'; return x; } int main() { struct s y = {4, 5, 6, "gogopower"}; printf("%d %d %d %s\n", y.a, y.b, y.c, y.str); f(y); printf("%d %d %d %s\n", y.a, y.b, y.c, y.str); y = f(y); printf("%d %d %d %s\n", y.a, y.b, y.c, y.str); } ``` 輸出: ``` 4 5 6 gogopower 4 5 6 gogopower 1 2 3 abcd ``` 有幾個問題點 - struct 是否可以 call by value? 的確可以, 在 C standard 有說可以, 且不是 call by address 的 syntax sugar, 可以看到組語有一大坨複製貼上的行為, 可預期到若 struct 很大, call by value 會很沒有效率 - struct 是否可以被 return? 也是可以的, C standard 說明同個 struct tag 的兩變數可以互相 assign, 所以 struct 可以被 return 就感覺很合理了 ## WTF6 ```c struct s { int n; char str[]; }; int main() { int m = 5; struct s *p = malloc(sizeof(struct s) + sizeof(char [m])); p->str[0] = 'e'; p->str[1] = 'd'; p->str[2] = 'i'; p->str[3] = 't'; p->n = m; printf("struct size: %ld\n", sizeof(struct s)); printf("%d %s\n", p->n, p->str); } ``` 輸出: ``` struct size: 4 5 edit ``` 根據 [C99](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf) p.103 原來 struct 還能這樣寫RRR