# 這還是 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; })
```