# 這還是 C 語言嗎? 淺談 Linux kernel 與 GUN Extension ## 目錄 - 前言 - 關於 C 語言標準 - 介紹 GNU C Extension - 使用語句表達式撰寫精確的巨集 ## 前言 在閱讀 Linux Kernel 的相關原始碼時,我們會看到大量使用 C 語言撰寫的程式碼,但是這一些 C 語言原始碼卻和我們平時學習的 C 語言程式碼似乎有一些不同之處,參考以下程式碼實作 ```c /* * Multiplies an integer by a fraction, while avoiding unnecessary * overflow or loss of precision. */ #define mult_frac(x, numer, denom)( \ { \ typeof(x) quot = (x) / (denom); \ typeof(x) rem = (x) % (denom); \ (quot * (numer)) + ((rem * (numer)) / (denom)); \ } \ ) ``` 或是以下 ```c /* * The double __builtin_constant_p is because gcc will give us an error * if we try to allocate the static variable to fmt if it is not a * constant. Even with the outer if statement. */ #define ftrace_vprintk(fmt, vargs) \ do { \ if (__builtin_constant_p(fmt)) { \ static const char *trace_printk_fmt __used \ __section("__trace_printk_fmt") = \ __builtin_constant_p(fmt) ? fmt : NULL; \ \ __ftrace_vbprintk(_THIS_IP_, trace_printk_fmt, vargs); \ } else \ __ftrace_vprintk(_THIS_IP_, fmt, vargs); \ } while (0) extern __printf(2, 0) int __ftrace_vbprintk(unsigned long ip, const char *fmt, va_list ap); extern __printf(2, 0) int __ftrace_vprintk(unsigned long ip, const char *fmt, va_list ap); ``` 上面這一些都是 C 語言,但並不是標準的 C 語言語法,而是 C Extensions (Using the GNU Compiler Collection (GCC)),在 Linux Kernel 或是 Driver 的開發中我們時常會見到他的身影,為了讓我們可以一窺 Linux Kernel 的面紗,是必須要對他有一定的了解。 ## 關於 C 語言標準 在 C 語言早期,各大編譯器廠商開發自己編譯器大多是各自開發,各自維護,久而久之,造成一個 C 語言的程式在某一個編譯器可以通過,在另外一個卻不會通過,而為了解決這個現象,美國國家標準協會 ANSI 和編譯器廠商以及各技術人員,考量到指令集架構,編譯對應的二進位指令等等,於 1989 年發表第一版 C 語言標準,史稱 C89,又稱為 ANSI C 標準。 C 語言標準主要規範以下: - 關鍵字,資料型別 - 運算規則,符號優先級 - 資料型別的轉換 - 變數的作用範圍 - 函數參數限制,函數 prototype 標準是一回事,但編譯器是不是支援,就是另外一個故事了... 就像是有 5G 標準,~~但我的手機不支援阿~~ 根據不同開發環境,為了性能優化等等,會對 C 語言標準進行一些擴充,而 GCC 對於 C 的擴充,就稱為 C Extensions (Using the GNU Compiler Collection (GCC)),以下為僅在 GCC 底下才能夠編譯通過的 C 語言程式碼 ```c int main(void) { int a[0];// 長度為 0 的 array void (*funcPtr[2])(void) = {[0 ... 1] = func1, [1] = *func2};//指定範圍初始化 } ``` 你知道嗎? C 裡面有 vector 喔~ ```c #include <stdio.h> typedef int v4si __attribute__ ((vector_size (16))); int main(void) { v4si a = {1,2,3,4}; v4si b = {3,2,1,4}; v4si c = a + b; for(int i = 0; i < 4; i++) printf("%d ", c[i]); } ``` output ``` 4 4 4 8 ``` C Extensions (Using the GNU Compiler Collection (GCC)) 添加了許多的擴充, ## C 語言,表達式 (expression), 語句 (statement), 程式碼區塊 (code block) ### 表達式 (expression) 表達式: 由操作符號 (operation) 以及操作數 (operand) 所組合而成的式子。 操作符號: 可以是算術運算符號,邏輯運算符號,賦值運算符號,比較運算符號。 操作數: 可以是常數,變數 以下都可以稱作是一個表達式 ```c 2 + 3 2 i = 2 + 3 i = i++ + 3 "Hello" ``` 可以看到,單一個常數 (字串),也可以當作是一個表達式 ### 語句 (statement) 語句: 在表達式後面加上一個分號 ';' 就構成了一個基本的語句,如以下 ```c i = 2 + 3; ``` ### 程式碼區塊 (code block) 將不同的語句,使用一個大括號包起來,就構成了一個程式碼區塊,在程式碼區塊中的變數,其作用域僅限於該區塊中。 ```c #include <stdio.h> int main(void) { int i = 3; printf("i = %d\n", i); { int i = 4; printf("i = %d\n", i); } printf("i = %d\n", i); return 0; } ``` output ``` i = 3 i = 4 i = 3 ``` ### 語句表達式 GNU C 允許我們在一個表達式裡面內嵌語句,可以在表達式裡面使用區域變數,for 迴圈等等,而這樣的表達式,我們稱為語句表達式 ``` ({表達式1; 表達式2; 表達式3}) ``` 以下為一個語句表達式的應用 ```c #include <stdio.h> int main(void) { int sum = 0; sum = ({ int s = 0; for(int i = 1; i <= 10; i++) s = s + i; s; }); printf("sum = %d\n", sum); return 0; } ``` 解釋: 語句表達式的值會等於最後一個表達式的值,這裡可以看到 sum 為一個語句表達式,而在這個語句表達式中,有三個語句,分別為 `int s = 0`, `for`, `s`,因此,最後 sum 這個語句表達式的值為 s。 語句表達式可以讓我們完成並且定義更加複雜的巨集,且可以避免巨集定義上所帶來的模糊不清的部分以及歧異,下面我們將通過 "求兩個數的最大值這個巨集來展示"。 ## 求兩個數的最大值巨集 ```c #define MAX(x, y) x > y? x : y ``` 上面這是一個直覺上的寫法,我們可以測試一下上面的結果 ```c #include <stdio.h> #define MAX(x,y) x > y? x : y int main(void) { printf("MAX: %d\n", MAX(3,4)); printf("MAX: %d\n", MAX(1 != 1, 1 != 2)); } ``` output ``` MAX: 4 MAX: 0 ``` 很明顯可以看到第二個結果是錯的,我們試著展開巨集了解一下 ```c printf("MAX: %d\n" 1 != 1 > 1 != 2? 1 != 1: 1 != 2) ``` 我們知道 '>' 的優先度大於 '!=',因此 ```c printf("MAX: %d\n" 1 != 1 > 1 != 2? 1 != 1: 1 != 2); printf("MAX: %d\n" 1 != 0 != 2? 1 != 1: 1 != 2); ``` 運算的順序發生了改變,而我們希望 ```c printf("MAX: %d\n" (1 != 1) > (1 != 2)? (1 != 1): (1 != 2)); ``` 為了確保運算順序不變,我們對巨集進行修改 ```c #define MAX(x,y) (x) > (y)? (x) : (y) ``` 但是,我們還是發現了漏洞 ```c #include <stdio.h> #define MAX(x,y) (x) > (y) ? (x) : (y) int main(void) { printf("MAX= %d\n", 3 + MAX(2,1)); } ``` output ``` MAX= 2 ``` 我們將巨集展開 ```c printf("MAX= %d\n", 3 + (2) > (1) ? (2) : (1)); ``` 因為 '+' 的順序大於 '>',因此 ```c printf("MAX= %d\n", 5 > (1) ? (2) : (1)); ``` 所以,我們為了避免這個情況發生,我們會希望 ```c printf("MAX= %d\n", 3 + ((2) > (1) ? (2) : (1))); ``` 對應到的巨集修改 ```c #define MAX(x,y) ((x) > (y) ? (x) : (y)) ``` 然後我們進行測試 ```c #include <stdio.h> #define MAX(x,y) ((x) > (y) ? (x) : (y)) int main(void) { int a = 2; int b = 6; printf("MAX= %d\n", MAX(a++, b++)); } ``` output ``` MAX= 7 ``` 我們預期的結果應該是比較完大小,之後進行自增運算,對於這個情況,我們就能夠使用語句表達式了,我們使用語句表達式定義這個巨集,並將巨集參數化 ```c #include <stdio.h> #define MAX(x,y) ({ \ int _x = x; \ int _y = y; \ _x > _y ? _x : _y;\ }) int main(void) { int a = 2; int b = 6; printf("MAX= %d\n", MAX(a++, b++)); } ``` MAX 為一個語句表達式,在語句表達式中使用了兩個區域變數,`_x` 和 `_y`,然後比較大小,最後表達式的值就是 MAX 這個語句表達式的值,但是這個有一個問題,只能夠比較兩個資料型別為整數的資料,我們可以繼續修改,讓他可以比較兩個任意資料型別的大小 ```c #include <stdio.h> #define MAX(type, x,y) ({ \ type _x = x; \ type _y = y; \ _x > _y ? _x : _y;\ }) int main(void) { printf("MAX= %d\n", MAX(int, 3,4)); printf("MAX= %f\n", MAX(float, 3.14, 5.88)); } ``` 我們通過傳入 type 讓他能夠對兩個任意型別的資料進行大小比較,但是,我們希望程式碼更加的優雅,我們嘗試不傳入 type,這時候可以使用 C Extensions (Using the GNU Compiler Collection (GCC)) 裡面的 typeof ```c #include <stdio.h> #define MAX(x,y) ({ \ typeof(x) _x = (x); \ typeof(y) _y = (y); \ _x > _y ? _x : _y;\ }) int main(void) { printf("MAX= %d\n", MAX(3,4)); printf("MAX= %f\n", MAX(3.14, 5.88)); } ``` 這下整個 MAX 巨集優雅了許多,但我們還需要確認,如果傳入兩個不同型別的資料進行比較,我們需要給予警告,我們進行以下修改 ```c #include <stdio.h> #define MAX(x,y) ({ \ typeof(x) _x = (x); \ typeof(y) _y = (y); \ (void) (&_x == &_y); \ _x > _y ? _x : _y;\ }) int main(void) { printf("MAX= %d\n", MAX(3,4)); printf("MAX= %f\n", MAX(3, 5.88)); } ``` output ``` test.c: In function 'main': test.c:5:17: warning: comparison of distinct pointer types lacks a cast (void) (&_x == &_y); \ ^ test.c:12:25: note: in expansion of macro 'MAX' printf("MAX= %f\n", MAX(3, 5.88)); ^~~ MAX= 4 MAX= 5.880000 ``` 成功出現了警告,這邊我們做的操作是直接轉型成指標,並且由於比較了兩個不同型別的指標,因此出現了警告,而我們就可以藉由這個警告,得知我們傳入了不同的資料型別進行比較,也就成功為 MAX 這個巨集加入了型別判斷的警告功能。 語句表達式,在 Linux kernel 中也是大量的被使用,可以做到更加精確的巨集定義,且避免一些漏洞發生,以下為部分實作 ```c #define min(x, y) ({ \ typeof(x) _min1 = (x); \ typeof(y) _min2 = (y); \ (void) (&_min1 == &_min2); \ _min1 < _min2 ? _min1 : _min2; }) #define max(x, y) ({ \ typeof(x) _max1 = (x); \ typeof(y) _max2 = (y); \ (void) (&_max1 == &_max2); \ _max1 > _max2 ? _max1 : _max2; }) ```