前言
這篇的標題定的很…白話(?) 主要是在看Linux kernel code的時候有時候常常會看到一堆define,有些define還會搭配井字號的寫法還會讓你很看不懂,所以重新寫一個小短篇順便幫自己複習一下。
然後井字號這種特殊符號又很難去search,所以標題就決定也這樣下,不知道未來遇到同樣問題的人搜尋起來會不會比較容易xD
來個小測驗,如果你覺得你夠懂#define了,下面這段Linux kernel code你看得懂嗎?
/* include/linux/init.h. line=172 */ |
比較陌生的應該是跟井字號的用法(# 和 ##),他們其實被稱作Preprocessor operators
所以這篇一開始會快速介紹define macro,接下來開始介紹和Preprocessor operators的搭配,對這部分有興趣的可以直接看最後一Part。
1分鐘學會#define macro
define macro的用法很簡單,快速舉個例子:
int main() {
printf("%d\n", MAX_LEVEL);
return 0;
}
Compiler在編譯時期就會把這個MAX_LEVEL直接替換成後面的100,就4這麼簡單。
define常被用來定義一些常量,或是一些config。例如你的Code有個部分會引用到版本號,那每次版本號一變動所有相關的code都要跟著改很麻煩,此時你可以#define VERSION 1.1
,然後把code都用VERSION來替代,這樣未來如果版本變了只需要改一次define的值就好。
- 此外,也可以透過define macro來節省一些function call,具體可以在後面的介紹觀察到
最後,為了和function, variable區分,define macro通常都會使用大寫。
define不只能用在常數
define也可以用在function,例如:
但要注意,上面這兩種寫法是錯誤的。因為define只是將後面的東西替換你的add(a, b)
,所以如果你在呼叫的時候這樣寫:
int main(){
int c = ADD(1, 2); // exec correct, c = 3
int d = ADD(1, 2) * 0; // wrong output, d = 1
}
可以發現d的值跟預想的不同,因為ADD(1, 2)
只是被替換成1+2
,並不是實際去執行了,所以d的assign右半邊實際上是1+2*0
,也就得到了1的output。
解決方法是在define前後加上括號,確保define被替換的內容也能正確地執行
所以define macro使用在function或condition(當然也可以拿來寫if else)的時候要很小心。
再看另一個例子:
a ^= b; \
b ^= a; \
a ^= b; \
}
void swap(int a, int b) {
a ^= b;
b ^= a;
a ^= b;
}
int main() {
int x = 1, y = 2;
std::cout << x << y << std::endl; // 12
SWAP(x, y);
std::cout << x << y << std::endl; // 21
swap(x, y);
std::cout << x << y << std::endl; // 21
return 0;
}
會發現SWAP
成功的將x y調換了,但swap()
卻沒有,為什麼呢?
- 因為swap()是call by value
- 而SWAP是define macro,實際上compiler是把那三行替換到
main()
中而不是呼叫一個function- 有注意到我一值強調替換嗎? 這是define macro很重要的行為要分辨清楚以後才不會出錯
然後有發現這個例子中define變成了多行的敘述了嗎? 沒錯,define當然可以分成多行來撰寫。
define也可以寫多行
如果define的敘述太長怎麼辦,一般建議程式碼一行不要超過79個字元,所以有時候有需要分行的需求,此時就可以這樣寫:
a ^= b; \
b ^= a; \
a ^= b; \
}
透過用斜線的方式來寫多行的define macro。
當#define碰上井字號
這些符號稱之Preprocessor operators,共有三種。下面快速總結,淺顯易懂:
- Stringizing operator (#): 用來將引數變成一個字串
- Charizing operator (#@): 用來將引數變成一個字元
- Token-pasting operator (##): 用來拼接引數
然後再搭配個簡單的例子就知道了:
|
了解了之後,最後回頭看我們在前言提到的那段kernel code:/* include/linux/init.h. line=172 */
static initcall_t __initcall_#
__attribute__((__section__(".initcall" #id ".init"))) = fn;
如果fn=module, id=3的話,那__define_initcall(fn, id)
就會變成
static initcall_t __initcall_module3 __used \ |
你答對了嗎?