在 linux kernel 或是一些 open source 專案中,經常看到許多高深莫測的 macro 用法,其中 Linux Kernel 中最經典的 macro 之一就是 container_of
,它看起來很奇怪,因為他使用了 { 和 } 包起來,看起來像是一個 function ,但這個 function 並沒有回傳任何值。
看完之後,你可能仍然不知道這個 macro 是在做什麼,想要真正理解這個 macro,你必須先了解什麼是「Statement Expression 」。
#define container_of(ptr, type, member) ({ \
void *__mptr = (void *)(ptr); \
static_assert(__same_type(*(ptr), ((type *)0)->member) || \
__same_type(*(ptr), void), \
"pointer type mismatch in container_of()"); \
((type *)(__mptr - offsetof(type, member))); })
回到本篇標題,標題中包含了「statements」、「declarations」和「expressions」這三個名詞,相信一定看的頭昏眼花。先了解了這三個概念之後,再來探討 GNU C 中的「Statement Expression 」。
Expression
expression 由 operator 和 operand 組成, operator 可以是變數或是數字,而 operand 可以分為許多種類型,你可以參考 C和C++ 運算子 wikipedia 中的介紹。根據 C 語言規格書對於 expression 的定義:
An expression is a sequence of operators and operands that specifies computation of a
value, or that designates an object or a function, or that generates side effects, or that
performs a combination thereof.
由 operator 和 operand 所組成的 expression 可以組合成許多不同的數值、指名一個 object 或 function ,甚至產生 side effect ,每個 expression 最終都會有一個 result 。以下來看一些例子:
"This is a string"
99
a+b
c = a > b ? a : b
3 << 2
func(a, b, c)
Statement
statement 代表一個要執行的動作,通常在 expression 後面加上 ; 就會形成 statement 。每個 statement 都是一個完整的執行單元,可以是賦值、迴圈、函數呼叫等等。
Declaration
宣告對於學習過 C 語言的人來說應該都知道,我之前曾經寫過一篇文章探討過 declaration 和 definition 的不同。
Block
由 { } 所包圍起來的稱為 block ,裡面可以包含多個 declaration 和 statement 。重要的是,在 block 內宣告或定義的變數,其生命週期也會在 block 結束後而終止。
int main()
{
int a = 3;
if (a == 3) {
int a = 5;
}
printf ("%d ", a);
}
在上面程式中,最終會輸出 3 ,在 if block 內定義的 a 隨著 block 的結束,其 scope 或稱 lifetime 也跟著結束。
介紹完以上概念,來探討什麼是 statement expresssion 。
Statement expression
在 GNU C 中,定義了 compound statement ,搭配 if, for 等組成,由 { 和 } 所圍起,內含多個 statements ,如果將 compound statement 再用 ( ) 包起來的話,這就是一個 statement expression 。
statement expression 也是一種 expression ,因此也如同一般 expression 一樣會產生一個結果 ,而 statement expression 的結果會放在最後一個 statement ,以下就是一個 statement expression 的範例 ,該 expression result 為 z
。
({ int x = 0, y = foo();
int z;
if (x > y) z = x;
else z = y;
z; })
為什麼需要 Statement Expression
statement expression 可以包含很複雜的運算在裏頭,但這樣用的優點又是什麼?
我們看以下的例子:
#define max(a, b) a > b ? a : b
這個 macro 有很多問題,如果今天傳進去的變數是有 side effect 的話,結果就會不如你的預期,
#define max(a,b) a > b ? a : b
int main()
{
int i = 1, j = 5;
printf("%d \n", max(i++, j++));
printf("%d \n", j);
return 0;
}
印出來的答案你會預期是 5 和 6 ,但答案其實是 6 和 7 ,這是因為在 macro 展開後, ++
的運算反而被執行兩次, expression 被重複執行,就稱為 double expression 。如果想讓 macro 運行的更加安全,就能夠靠 statement expression 來保護,如以下透過 statement expression 所寫的 max macro ,另外透過兩個變數來暫存參數,即可避免上述的 side effect 。
#define max (a, b) ({ int _a = a; int _b = b;
_a > _b ? _a: _b })
不過 max 的 macro 在 Linux kernel 內其實有更多的保護機制,例如可以用 typeof()
來取得傳入參數的型別,比對兩個參數是否有相同的型別等,這裡我們就不再延伸探討。
使用 Statement Expression 可以讓 macro 更加安全且方便使用。透過 Statement Expression 來組合 macro ,展開的結果會變成一個 expression ,並可以直接回傳結果。這樣就不會因為展開 macro 的 side effect 而導致意料之外的結果。在學習了 Statement Expression 後,我們相信在閱讀 Linux kernel 程式碼時,將會更容易理解其中複雜的 macro 用法。
Reference
Updated on 2023-01-08 16:10:44 星期日