learn from Few lesser known tricks, quirks and features of C
斟酌使用
有些章節沒有詳細說明通常是因為在原文中也沒詳細說明,有時間再補
用 pointer 表示 array(decay-to-pointer):
如果是單純要存取 arr 則直接用 arr[] 即可,也比較不容易搞錯
第二種做法有個好處是在撰寫多維陣列空間分配相當好用:
這樣分配出來就是一個平坦的 100 x 100 一維空間,透過 p_arr 可以用二維方式去存取
初學的用法應該是像這種:
抽象來說兩者差不多,但實際上兩者的記憶體分布長的不太一樣
用 decay-to-pointer 還可以結合 VLA 來分配空間:
另外一個使用 pointer to array 的優點是如果直接把 array 當作參數傳給 function ,則 array 會退化成 pointer 並且無法保存大小的資訊,但是如果是用 pointer to array ,那在 dereference 的時候可以拿到該 array 的大小:
介紹 , 當作 operator 的特性, wiki 有更多介紹 (https://en.wikipedia.org/wiki/Comma_operator#Examples)
使用上也有一些常見的陷阱:
一種比較酷但是不太直覺的寫法:
這樣 errno 會被設定成 ERRORCODE 然後 function 會 return -1
等價於
或是拿來避免撰寫 {} 用,不過我個人覺得避免 {} 沒啥太大意義
基本上鍵盤只要有 ASCII 字符集就能寫 code ,但不是每個鍵盤都有相對應的符號,所以 C 語言允許某些字元組合來表示必要的符號:
所以 int a[10];
可以寫成 int a<:10:>;
其他還有
三元表示或是直接用字串表示 iso646.h
C99 中可以在 {} intializer 指定要 intial 哪個 index (for array)或是 intial 哪個 field(for struct)
透過 cast 成特定結構 + brace intializer 即可不宣告而定義一個匿名的結構:
另外要注意的是 compound literal 是 lvalue ,可以對他 assign 或是取地址之類的操作
可以用 '' 包住多個字元當作 enum 的 value
這個技巧具體實作要看編譯器怎麼決定,開發中極不推薦使用,但是可以拿來定位一些記憶體位置
可以在 struct 內指定變數要用幾個 bit 來保存,常用於一些網路封包設計或是對記憶體空間斤斤計較的場景
拿來對齊用,比如用 char: 0
是用 byte 來對齊,如果是 int: 0
就是對齊 4bytes ,或者是當作邊界使用,像是
這樣 a b 會處在同個 byte 中,但是如果改成:
這樣 a 和 b 中間就會空一格變成 2 bytes , char : 0
實際上是 a + 7 個 bit 組成
換個例子:
此時 layout 會是:
unsigned short
unsigned short
將第一個 byte 的 5 bit 算在內,但使用上沒什麼問題告知 compiler 此變數有可能以其他方式訪問,禁止對其進行優化或 reorder
告知 compiler 此 pointer 在其生命週期內都不會有任何其他 pointer 進行訪問,可以安心的進行最佳化
告知 compiler 建議將此變數存放到 register 中,不要放到 memory ,副作用是無法得到該變數的地址但是可以計算該變數的大小,現代 compiler 不太需要這種提示,通常 compiler 都會做的比你還好,只有在 embedded 的環境下可能還有點用
透過將 array 變數放到結構最後並且不加上 subscript 來表示這是任意長度的 array
如果什麼都不加那 struct A 就只有 a 的大小,但如果是
大小還是 a 的大小,但可以透過 b 來訪問後面 16 bytes ,不過感覺超容易越界 XD
這種技巧在結構是:
組合應該很好用
flexible array 在 GNU extension 也可以用
表示,更早期的作法是
跟以下方式相比有個優點是可以減少 allocate 次數和訪問不同記憶體的次數
當然這種方式也有幾個缺點,望慎用之:
%n 可以得到當前字串長度的位址
輸出成:
但我第一次認識 %n 是在 format string vulnerability 中,用法是利用 %n 寫值到參數的特性,如果有 fmt bug 我們就可以利用 %n 把字串長度寫到某個 stack 上的變數達到 memory corruption
更屌的用法可以參考 printf-tac-toe,簡單講是利用 printf 做一個圖靈完備機,其中利用 %n + puts 會把 register 值 push 到 stack 的特性做到 loop 的效果
設定精度可以用質樸的:
寫好最小精度後透過 snprintf 組裝成 %.2f
然後送到 printf 裡面( %%
應該是把 % 字串畫)
可以用以下方式完成一樣的事情
可以找到 %#, %e, %-, %+, %j, %g, %a 之類的格式化字串
switch 的 case 其實相當於 label ,因此可以實際上在 switch 裡面任意跳轉,經典的例子是 Duff's Device ,好像是 80 年代的最佳化方式:
這是個無聊的笑話可以騙別人說 C 有 -->
這個 operator ,實際上是 --
+ >
,當然這可以編譯
array 中的 square braces 實際是語法糖,完成 *(arr + i)
的工作,實際上他可以用這種形式來寫(但極不推薦):
可以用負數當作 index 取 array 的值,有些檢查副檔名的寫法會這樣寫:
字串常數可以用 ""
""
兩個雙引號連在一起即可:
可以把 &&
和 ||
不用 if 括號直接使用,但是這樣好難看…
這個意義不明…
struct A 是一種 data type
這樣也是一種 type ,只是之後用起來很麻煩要一直 struct { int a;} A;
這樣宣告
第一種宣告也可以宣告在 return value 的位置:
nested struct 宣告:
這樣其實只有宣告 datatype 但是沒有定義,所以實際上無法從 Foo 去訪問 y ,但是宣告在 Foo 裡面的 Bar 也可以拿來使用這點沒問題
透過 {} 可以在定義的時候順便 assign value 到 array 或是 struct
C99 之後可以在 array index 宣告的時候寫上 static const 等修飾詞
貌似可以幫助 compiler 進行最佳化
文中有一段敘述是這樣的:
如果單單用 array 名字不加上 [] 則該名字表示 array 第一個元素的地址,或者將 array[] 宣告在參數中也是會轉型成 pointer ,但這樣會丟失掉 array size 等資訊, C99 允許 array 宣告在 parameter 時對 index 加上 static 保存其長度大小的資訊,加上 const 則保存,
有這些資訊可以幫助 compiler 做 optimize ,但是要切記這個只是提醒,如果 caller 沒遵守的話是 UB
CMObALL
BOOST_PP_OVERLOAD
Can macros be overloaded by number of arguments? - Stack Overflow
這個感覺不算黑魔法,我覺得滿常見的,在寫 handler 就會用到:
X Macro - Wikipedia
Wikibooks on X macros
C Programming/Serialization/X-Macros
Real-world use of X-Macros - Stack Overflow
What are X-macros? – Arthur O'Dwyer
X macro: most epic C trick or worst abuse of preprocessor? / Arch Linux Forums
The Most Elegant Macro – Phillip Trudeau
這個用意我猜是在呼叫 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
更多關於 macro 的用法大全
寫過 C++ 就知道 C++ 函數的參數可以放上 default 值:
C 的話可以透過 macro 做到類似的事情:
透過先指定 FooParam 的 field value 後再用 VA_ARGS 去覆蓋掉先前給的 value
但是這樣有個問題,要使用 foo function 就一定要先給參數名字,不能像從前:
foo(1,2,3,4);
但是如果再額外加上一個沒用到的 dummy variable 就可以這樣寫:
而且一定要從 abcd 中區分開來,單獨拿出來寫一個 statement
如果 struct 有一坨 field 需要一次性的移動,可以用 union + nested structure 達成:
猜測原本是長這樣:
但我們需要綁定 field2 和 field3 ,但他們彼此之間操作上不太適合綁定? ,所以用 union + nested structure ,這樣可以透過 a.sub 一次移動 field2 和 field3 ,使用時也可以改成 a.field2 不必用 a.sub.field2
sscanf 可以做到簡易的 regex (僅限於 charactor)
CORE library ,不用 virtual machine 或是 interpreter ,他把常見在各作業系統的執行檔格式一次編譯出來然後組合在一起
在 C 中使用 assembly ,不過這是 compiler extension
用 OOP 方式寫 C ,文中有提供數個連結供參考,不過我覺得其實只要知道 C++ 背後的實作原理那不用參考文章也能弄得出 OOP 的 C ,有時間再補
C11 有 _Generic ,但 C99 可以用 datatype99.h 達成類似的事情(還是用 preprocessor 達成)
這我還沒仔細研究要怎麼用
用以下寫法讓編譯器在編譯階段就透過 error 告知 data type 大小
不確定意義何在…
Coroutines in c
Coroutines#C - Wikipedia
Does C support Coroutines ? - Quora
c-coroutine
libaco
libtask
neco
How to get fired using switch and statement
不確定是不是跟 setcontext 之類的 function 相關
CCAN 貌似跟 perl 的 CPAN 有關,後者是一個紀錄各種 perl code snippets 的網站
現階段 Generic 不能匹配 array 但是有個 trick 是把 array 用 function pointer 包裝: