Statement Expression – Statements and Declarations in Expressions

在 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

  1. https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html
  2. Linux 核心原始程式碼巨集: maxmin

Updated on 2023-01-08 16:10:44 星期日

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *