---
# System prepended metadata

title: WTF C
tags: [C]

---

---
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







