# C 語言黑魔法
learn from [Few lesser known tricks, quirks and features of C](https://blog.joren.ga/less-known-c)
**斟酌使用**
**有些章節沒有詳細說明通常是因為在原文中也沒詳細說明,有時間再補**
## Array Pointer
用 pointer 表示 array(decay-to-pointer):
```c
int arr[10];
// 1.
int *p_arr = arr;
p_arr[2] = 1;
// 2.
int (*p_arr2)[10] = &arr;
(*p_arr2)[2] = 1;
```
如果是單純要存取 arr 則直接用 arr[] 即可,也比較不容易搞錯
第二種做法有個好處是在撰寫多維陣列空間分配相當好用:
```c
int (*p_arr)[100][100] = malloc(sizeof(*p_arr));
```
這樣分配出來就是一個平坦的 100 x 100 一維空間,透過 p_arr 可以用二維方式去存取
初學的用法應該是像這種:
```c
int **a = malloc(sizeof(int*)*100);
for(int i = 0;i < 100;i++) {
*(a + i) = malloc(sizeof(int)*100);
}
```
抽象來說兩者差不多,但實際上兩者的記憶體分布長的不太一樣
用 decay-to-pointer 還可以結合 VLA 來分配空間:
```c
int (*a)[N] = malloc(sizeof(*a));
```
另外一個使用 pointer to array 的優點是如果直接把 array 當作參數傳給 function ,則 array 會退化成 pointer 並且無法保存大小的資訊,但是如果是用 pointer to array ,那在 dereference 的時候可以拿到該 array 的大小:
```c=
void foo(int arr[10]) {
// sizeof(*arr) == 4
// sizeof(arr) == 8
}
void bar(int (*arr)[10]) {
// sizeof(*arr) == 40
// sizeof(arr) == 8
}
```
## Comma operator
介紹 , 當作 operator 的特性, wiki 有更多介紹 (https://en.wikipedia.org/wiki/Comma_operator#Examples)
使用上也有一些常見的陷阱:
* assignment operator 優先權 > comma operator
一種比較酷但是不太直覺的寫法:
```c
if(/* ... */) {
return (errno = ERRORCODE, -1);
}
```
這樣 errno 會被設定成 ERRORCODE 然後 function 會 return -1
等價於
```c
if(/* ... */) {
errno = ERRORCODE;
return -1;
}
```
或是拿來避免撰寫 {} 用,不過我個人覺得避免 {} 沒啥太大意義
## Digraphs, trigraphs and alternative tokens
基本上鍵盤只要有 ASCII 字符集就能寫 code ,但不是每個鍵盤都有相對應的符號,所以 C 語言允許某些字元組合來表示必要的符號:
* <: -> [
* :> -> ]
...
諸如此類,當出現這些字元組合, preprocessor 會將其替換成相對應的字元
所以 `int a[10];` 可以寫成 `int a<:10:>;`
其他還有
三元表示或是直接用字串表示 `iso646.h`
## Designated initializer
C99 中可以在 {} intializer 指定要 intial 哪個 index (for array)或是 intial 哪個 field(for struct)
```c
int a[] = {0,1,2,[5] = 10, [9] = 11};
struct Foo {
int x, y, z;
};
struct Foo f = {.x = 10, .z = 20, .y = 30};
// mix
struct Foo f_a[] = {[10] = {.x = 10, .z = 20, .y = 30}};
```
## Compound literals
透過 cast 成特定結構 + brace intializer 即可不宣告而定義一個匿名的結構:
```c
struct Foo {
int x, y;
};
void bar(struct Foo f) {
}
int main() {
bar((struct Foo){.x = 10, .y = 20});
}
```
另外要注意的是 compound literal 是 lvalue ,可以對他 assign 或是取地址之類的操作
```c
(struct Foo){}.x = 10; // valid
&(struct Foo){}; // valid
```
## Multi-character constants
可以用 '' 包住多個字元當作 enum 的 value
```c
enum state {
WAIT = 'WAITING',
END = 'END',
};
```
這個技巧具體實作要看編譯器怎麼決定,開發中極不推薦使用,但是可以拿來定位一些記憶體位置
## Bit fields
可以在 struct 內指定變數要用幾個 bit 來保存,常用於一些網路封包設計或是對記憶體空間斤斤計較的場景
```c
struct Foo {
int a: 2;
int b: 2;
};
```
## 0 bit fields
拿來對齊用,比如用 `char: 0` 是用 byte 來對齊,如果是 `int: 0` 就是對齊 4bytes ,或者是當作邊界使用,像是
```c
struct A {
char a: 1;
char b: 1;
}
```
這樣 a b 會處在同個 byte 中,但是如果改成:
```c
struct A {
char a: 1;
char : 0;
char b: 1;
}
```
這樣 a 和 b 中間就會空一格變成 2 bytes , `char : 0` 實際上是 a + 7 個 bit 組成
換個例子:
```
struct bar {
unsigned char x : 5;
unsigned short : 0;
unsigned char y : 7;
}
```
此時 layout 會是:
* 第一個 byte 的 5 個 bit for x
* 第一個 byte 的 5 個 bit + 剩下的 3 bit + 8 個 bit = 16bit 屬於 `unsigned short`
* 雖然 `unsigned short` 將第一個 byte 的 5 bit 算在內,但使用上沒什麼問題
* 第三個 byte 的 7 個 bit 屬於 y
## volatile
告知 compiler 此變數有可能以其他方式訪問,禁止對其進行優化或 reorder
## restrict
告知 compiler 此 pointer 在其生命週期內都不會有任何其他 pointer 進行訪問,可以安心的進行最佳化
## register
告知 compiler 建議將此變數存放到 register 中,不要放到 memory ,副作用是無法得到該變數的地址但是可以計算該變數的大小,現代 compiler 不太需要這種提示,通常 compiler 都會做的比你還好,只有在 embedded 的環境下可能還有點用
## Flexible array member
透過將 array 變數放到結構最後並且不加上 subscript 來表示這是任意長度的 array
```
struct A {
int a;
char b[];
};
```
如果什麼都不加那 struct A 就只有 a 的大小,但如果是
```
void foo() {
struct A *a = malloc(sizeof(struct A) + 16);
}
```
大小還是 a 的大小,但可以透過 b 來訪問後面 16 bytes ,不過感覺超容易越界 XD
這種技巧在結構是:
* header
* 固定欄位大小
* data
* 根據 header 決定 data 長度
組合應該很好用
flexible array 在 GNU extension 也可以用
```
struct A {
int a;
char b[0];
}
```
表示,更早期的作法是
```
struct A {
int a;
char b[1];
}
```
跟以下方式相比有個優點是可以減少 allocate 次數和訪問不同記憶體的次數
```
struct A {
int a;
char *b;
}
```
當然這種方式也有幾個缺點,望慎用之:
* 表達式變複雜
* 容易浪費空間
* 如果 array 其實可以塞進 struct 裡面但是用 flexible array 的話就會浪費掉那些沒在用的空間
* 讓靜態分析工具誤會
## %n format specifier
%n 可以得到當前字串長度的位址
```
int pos1, pos2;
const char* str_of_unknown_len = "we don't care about the length of this";
printf("Write text of unknown %n(%s)%n length\n", &pos1, str_of_unknown_len, &pos2);
printf("%*s\\%*s/\n", pos1, " ", pos2-pos1-2, " ");
printf("%*s", pos1+1, " ");
for (int i = pos1+1; i < pos2-1; ++i) {
putc('-', stdout);
}
putc('\n', stdout);
```
輸出成:
```
Write text of unknown (we don't care about the length of this) length
\ /
--------------------------------------
```
但我第一次認識 %n 是在 format string vulnerability 中,用法是利用 %n 寫值到參數的特性,如果有 fmt bug 我們就可以利用 %n 把字串長度寫到某個 stack 上的變數達到 memory corruption
更屌的用法可以參考 [printf-tac-toe](https://github.com/carlini/printf-tac-toe/issues/1),簡單講是利用 printf 做一個圖靈完備機,其中利用 %n + puts 會把 register 值 push 到 stack 的特性做到 loop 的效果
## %.* (minimum field width) format specifier
設定精度可以用質樸的:
```
char fmt_buf[MAX_BUF];
snprintf(fmt_buf, MAX_BUF, "%%.%df", prec);
printf(fmt_buf, num);
```
寫好最小精度後透過 snprintf 組裝成 `%.2f` 然後送到 printf 裡面( `%%` 應該是把 % 字串畫)
可以用以下方式完成一樣的事情
```
printf("%.*f", prec, num);
```
## Other less known format specifiers
請參照 [ §7.21.6.1](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf#%5B%7B%22num%22%3A703%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C0%2C792%2C0%5D) [§7.21.6.2](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf#%5B%7B%22num%22%3A719%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C0%2C792%2C0%5D)
可以找到 %#, %e, %-, %+, %j, %g, %a 之類的格式化字串
## Interlacing syntactic constructs
switch 的 case 其實相當於 label ,因此可以實際上在 switch 裡面任意跳轉,經典的例子是 Duff's Device ,好像是 80 年代的最佳化方式:
```
send(to, from, count)
register short *to, *from;
register count;
{
register n = (count + 7) / 8;
switch (count % 8) {
case 0: do { *to = *from++;
case 7: *to = *from++;
case 6: *to = *from++;
case 5: *to = *from++;
case 4: *to = *from++;
case 3: *to = *from++;
case 2: *to = *from++;
case 1: *to = *from++;
} while (--n > 0);
}
}
```
## --> "operator"
這是個無聊的笑話可以騙別人說 C 有 `-->` 這個 operator ,實際上是 `--` + `>` ,當然這可以編譯
## idx[arr]
array 中的 square braces 實際是語法糖,完成 `*(arr + i)` 的工作,實際上他可以用這種形式來寫(但極不推薦):
```c
// arr[5] = 0;
5[arr] = 0;
```
## Negative array indexes
可以用負數當作 index 取 array 的值,有些檢查副檔名的寫法會這樣寫:
```c
if(filename_end[-4] == '.' && filename_end[-3] == 'r' && filename_end[-2] == 'a' && filename_end[-1] == 'r')
```
## Constant string concatenation
字串常數可以用 `""` `""` 兩個雙引號連在一起即可:
```c
const char a[] = "aaa" \
"bbb";
```
## Using && and || as conditionals
```
#include <stdio.h>
#include <stdbool.h>
int main(void)
{
1 && puts("Hello");
0 && puts("I won't");
1 && puts("World!");
0 && puts("be printed");
1 || puts("I won't be printed either");
0 || puts("But I will!");
true && (9 > 2) && puts("9 is bigger than 2");
isdigit('9') && puts("9 is a digit");
isdigit('n') && puts("n is a digit") || puts("n is NOT a digit!");
return 0;
}
```
可以把 `&&` 和 `||` 不用 if 括號直接使用,但是這樣好難看...
## Compile time assumption checking using enums
這個意義不明...
```c
#define D 1
#define DD 2
enum CompileTimeCheck
{
MAKE_SURE_DD_IS_TWICE_D = 1/(2*(D) == (DD)),
MAKE_SURE_DD_IS_POW2 = 1/((((DD) - 1) & (DD)) == 0)
};
```
## 任意位置定義 struct
```c
struct A {
int a;
};
```
struct A 是一種 data type
```c
struct {
int a;
};
```
這樣也是一種 type ,只是之後用起來很麻煩要一直 `struct { int a;} A;` 這樣宣告
第一種宣告也可以宣告在 return value 的位置:
```
struct Foo {int a;} foo() {
struct Foo x = {.a = 10;};
return x;
}
```
## "Nested" struct definition is not kept nested
nested struct 宣告:
```c
struct Foo {
int x;
struct Bar {
int y;
};
};
```
這樣其實只有宣告 datatype 但是沒有定義,所以實際上無法從 Foo 去訪問 y ,但是宣告在 Foo 裡面的 Bar 也可以拿來使用這點沒問題
## Flat initializer lists
透過 {} 可以在定義的時候順便 assign value 到 array 或是 struct
```c
int arr[3][3] = {{1,2,3}, {4,5,6}, {7,8,9}}; // array
struct Foo {
int a;
char *name;
};
struct Foo f[2] = {1, "abcd", 2, "efgh", 3, "ijkl"};
```
## Static array indices in function parameter declarations
C99 之後可以在 array index 宣告的時候寫上 static const 等修飾詞
```c
int n;
void foo(int arr[static 10]); // arr points to the first of at least 10 ints
void foo(int arr[const 10]); // arr is a const pointer
void foo(int arr[const]); // const pointer to int
void foo(int arr[static const n]); // arr points to at least n ints (VLA)
```
貌似可以幫助 compiler 進行最佳化
文中有一段敘述是這樣的:
如果單單用 array 名字不加上 [] 則該名字表示 array 第一個元素的地址,或者將 array[] 宣告在參數中也是會轉型成 pointer ,但這樣會丟失掉 array size 等資訊, C99 允許 array 宣告在 parameter 時對 index 加上 static 保存其長度大小的資訊,加上 const 則保存,
有這些資訊可以幫助 compiler 做 optimize ,但是要切記這個只是提醒,如果 caller 沒遵守的話是 UB
## Macro Overloading by Argument List Length
[CMObALL](https://github.com/Jorengarenar/CMObALL)
[BOOST_PP_OVERLOAD](https://www.boost.org/doc/libs/master/libs/preprocessor/doc/ref/overload.html)
[Can macros be overloaded by number of arguments? - Stack Overflow](https://stackoverflow.com/q/16683146/10247460)
```c
#include <stdio.h>
#include "cmoball.h"
#define NoA(...) CMOBALL(FOO, __VA_ARGS__)
#define FOO_3(x,y,z) "Three"
#define FOO_2(x,y) "Two"
#define FOO_1(x) "One"
#define FOO_0() "Zero"
int main()
{
puts(NoA());
puts(NoA(1));
puts(NoA(1,1));
puts(NoA(1,1,1));
return 0;
}
```
## Function types
這個感覺不算黑魔法,我覺得滿常見的,在寫 handler 就會用到:
```c
typedef void (*func)(int);
// now you can use func to represent void (*)(int);
func f;
```
## X-Macros
[X Macro - Wikipedia](https://en.wikipedia.org/wiki/X_Macro)
[Wikibooks on X macros](https://en.wikibooks.org/wiki/C_Programming/Preprocessor#X-Macros)
[C Programming/Serialization/X-Macros](https://en.wikibooks.org/wiki/C_Programming/Serialization#X-Macros)
[Real-world use of X-Macros - Stack Overflow](https://stackoverflow.com/q/6635851/10247460)
[What are X-macros? – Arthur O'Dwyer](https://quuxplusone.github.io/blog/2021/02/01/x-macros/)
[X macro: most epic C trick or worst abuse of preprocessor? / Arch Linux Forums](https://bbs.archlinux.org/viewtopic.php?id=272242)
[The Most Elegant Macro – Phillip Trudeau](https://philliptrudeau.com/blog/x-macro)
## Named function parameters
```c
struct _foo_args {
int num;
const char* text;
};
#define foo(...) _foo((struct _foo_args){ __VA_ARGS__ })
int _foo(struct _foo_args args)
{
puts(args.text);
return args.num * 2;
}
int main(void)
{
int result = foo(.text = "Hello!", .num = 8);
return 0;
}
```
這個用意我猜是在呼叫 function 給參數的時候可以加上參數變數名稱,增加可讀性(?),原理是利用 compound literal 建立一個匿名的 arg struct (`(struct _foo_args){}`), struct 內放上參數名稱,這樣使用上就可以用 struct 的 designed initializer 去 assign (`{.text="Hello!", .num=8}`),另外因為 arg struct 內的 field name, 數量不是固定,因此用 `__VA_ARGS__` 去拿到所有參數並當作 compound literal 的值
[Variadic-Macros](https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html)
[更多關於 macro 的用法大全](https://gcc.gnu.org/onlinedocs/cpp/Macros.html)
## Combining default, named and positional arguments
寫過 C++ 就知道 C++ 函數的參數可以放上 default 值:
```c++
void foo(int a = 1, int b = 2, int c = 3) {}
int main() {
// all valid
foo(1);
foo(1,2);
foo(1,2,3);
}
```
C 的話可以透過 macro 做到類似的事情:
* 參數預設值
```c
typedef struct { int a,b,c,d; } FooParam;
#define foo(...) foo((FooParam){ __VA_ARGS__ })
void (foo)(FooParam p);
// same with above
#define foo(...) foo((FooParam){ .a=1, .b=2, .c=3, .d=4, __VA_ARGS__})
```
透過先指定 FooParam 的 field value 後再用 __VA_ARGS__ 去覆蓋掉先前給的 value
但是這樣有個問題,要使用 foo function 就一定要先給參數名字,不能像從前:
`foo(1,2,3,4);`
但是如果再額外加上一個沒用到的 dummy variable 就可以這樣寫:
```c
typedef struct { int _; int a,b,c,d; } FooParam;
#define foo(...) foo((FooParam){ .a=1, .b=2, .c=3, .d=4, ._=0, __VA_ARGS__})
```
而且一定要從 abcd 中區分開來,單獨拿出來寫一個 statement
```
foo(); // a=1, b=2, c=3, d=4
foo(.a=4, .b=5); // a=4, b=5, c=3, d=5
foo(4, 5); // a=4, b=5, c=3, d=5
foo(4, 5, .d=8); // a=4, b=5, c=3, d=8
```
## Abusing unions for grouping things into namespaces
如果 struct 有一坨 field 需要一次性的移動,可以用 union + nested structure 達成:
```c
struct a {
int field1;
union {
struct {
int field2;
int field3;
};
struct {
int field2;
int field3;
} sub;
};
};
```
猜測原本是長這樣:
```c
struct a {
int field1;
int field2;
int field3;
};
```
但我們需要綁定 field2 和 field3 ,但他們彼此之間操作上不太適合綁定? ,所以用 union + nested structure ,這樣可以透過 a.sub 一次移動 field2 和 field3 ,使用時也可以改成 a.field2 不必用 a.sub.field2
## Matching character classes with sscanf()
sscanf 可以做到簡易的 regex (僅限於 charactor)
```c
int len = 0;
char buf[256];
int read_token = sscanf(input, "%255[a-zA-Z_]", buf, &len);
if (read_token) { /* do something */ }
```
```c
int len = 0;
char buf[256];
sscanf(input, "%255[\r\n]%n", buf, &len);
input += len;
```
## Garbage collector
[Boehm GC](https://www.hboehm.info/gc/)
## Cosmopolitan Libc
CORE library ,不用 virtual machine 或是 interpreter ,他把常見在各作業系統的執行檔格式一次編譯出來然後組合在一起
## Inline assembly
在 C 中使用 assembly ,不過這是 compiler extension
<li><a href="https://wiki.osdev.org/Inline_assembly">Inline Assembly - OSDev Wiki</a></li>
<li><a href="https://en.cppreference.com/w/c/language/asm">Inline assembly - cppreference.com</a></li>
<li><a href="https://learn.microsoft.com/en-us/cpp/c-language/inline-assembler-c">Inline Assembler (C) | Microsoft Learn</a></li>
<li><a href="https://developer.arm.com/documentation/100748/0619/Using-Assembly-and-Intrinsics-in-C-or-C---Code/Writing-inline-assembly-code">Writing inline assembly code - Arm Compiler for Embedded User Guide</a></li>
<li><a href="https://www.cs.uaf.edu/courses/cs301/2014-fall/notes/inline-assembly/">Inline Assembly in C/C++ - University of Alaska Fairbanks</a></li>
## Object Oriented Programming
用 OOP 方式寫 C ,文中有提供數個連結供參考,不過我覺得其實只要知道 C++ 背後的實作原理那不用參考文章也能弄得出 OOP 的 C ,有時間再補
* [Object-Oriented Programming in C - Quantum Leaps](https://www.state-machine.com/oop)
* [Object-orientation in C - Stack Overflow](https://stackoverflow.com/q/415452/10247460)
* [Object-Oriented Programming With ANSI C](https://www.cs.rit.edu/~ats/books/ooc.pdf)
* [C Traps and Pitfalls](https://www.cs.tufts.edu/comp/40/docs/CTrapsAndPitfalls.pdf)
* ["you can have something like interfaces and virtual methods by using function pointers"](https://www.reddit.com/r/C_Programming/comments/mqk338/interesting_ways_to_use_c/guhenr5/)
## Metaprogramming
C11 有 _Generic ,但 C99 可以用 datatype99.h 達成類似的事情(還是用 preprocessor 達成)
```c
#include <datatype99.h>
datatype(
BinaryTree,
(Leaf, int),
(Node, BinaryTree *, int, BinaryTree *)
);
int sum(const BinaryTree *tree) {
match(*tree) {
of(Leaf, x) return *x;
of(Node, lhs, x, rhs) return sum(*lhs) + *x + sum(*rhs);
}
return -1;
}
```
> 這我還沒仔細研究要怎麼用
## Evaluate sizeof at compile time by causing duplicate case error
用以下寫法讓編譯器在編譯階段就透過 error 告知 data type 大小
```
int foo(int c)
{
switch (c) {
case sizeof (struct Foo): return c + 1;
case sizeof (struct Foo): return c + 2;
}
}
```
> 不確定意義何在...