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