若是你是 C++ 程序員,應該接觸過 C++11 裏的 decltype 操做符,它的做用是自動推導表達式的數據類型,以解決泛型編程中有些類型由模板參數決定而難以(甚至不可能)表示的問題。其實這個特性在 C 語言中也早有相似的實現,GNU C 標準中的一個擴展特性 typeof (PS: 不是 typedef)做用與 decltype 相似,咱們來看看這個關鍵字該怎麼用。linux
先來看一個最簡單的例子:程序員
// demo 01
int var = 666;
typeof(int *) pvar = &var;
printf("pvar:\t%p\n", pvar);
printf("&var:\t%p\n", &var);
printf("var:\t%d\n", var);
printf("*pvar:\t%d\n", *pvar);編程
咱們先定義了一個 int 型變量 var,而後再定義一個指針型變量指向 var,通常咱們就直接 int *xxx = &xx,可是咱們爲了演示 typeof 的用法,就不要這麼直接了,typeof 是自動推導後面 ( ) 裏的數據類型,因此 typeof(int *) 直接推導出了 int * 型,用這個類型聲明瞭 pvar 並將其初始化爲 var 的地址,輸出結果應該就顯而易見了,這是在個人機器上的輸出:數組
好吧我認可上面那個例子是吃力不討好,明明寫個 int * 簡單又明瞭,非得加個 typeof 搞得這麼晦澀,其實 typeof 的功效在於其可以自動推導表達式類型,好比咱們把剛纔的 typeof(int *) 改爲 typeof(&var),它也會自動推導出 &var 的類型 —— int * 型,你能夠本身試一下,原理是同樣的,這樣的話,當遇到一個很是複雜的表達式咱們很難推斷其類型的時候,typeof 就頗有用了。另外有一點要注意:typeof 是 GNU C 標準裏特有的擴展,標準的 ISO C 並無這個關鍵字,因此在編譯的時候不能加任何 ISO 的 C 標準選項,不然會報錯,好比編譯上面的代碼我加入了一個 -std=c90 的選項,編譯器就會有提示一堆 error:函數
解決的方法很簡單,把 -std=c90 改爲 -std=gnu90 即 GNU 的標準便可。指針
再來幾個例子,好比對象
// demo 02
int *pvar = NULL;
typeof(*pvar) var = 999;
printf("var:\t%d\n", var);字符串
這個例子是先定義了一個整型指針變量 pvar,typeof 後面括號裏的表達式爲*pvar,pvar 的類型爲 int * 型,那 *pvar 固然就被解析爲 int 型,因此用這個類型聲明的變量 var 也是 int 型,就至關於 int var = 999; 輸出結果以下:get
再來:原型
// demo 03
int *pvar = NULL;
typeof(*pvar) var[4] = {11, 22, 33, 44};
for (int i = 0; i < 4; i++)
printf("var[%d]:\t%d\n", i, var[i]);
此次 typeof 解析出來的類型跟上一個同樣,區別是 var 是一個包含四個元素的數組,至關於 int var[4] = {...}; 輸出以下:
此次來個有點水平的:
// demo 04
typeof(typeof(const char *)[4]) pchar = {"hello", "world", "good", "night"};
for (int i = 0; i < 4; i++)
printf("pchar[%d]:\t%s\n", i, pchar[i]);
此次看起來就比較複雜了,考驗你指針功底的時候到了,他嵌套了兩層 typeof,咱們一層一層的往外剝,先看最裏層,typeof 先解析出一個 const char * 類型,有經驗的 C 程序員應該立刻就能聯想到字符串了吧,然後面又跟着一個 [4],說明這是一個包含四個字符串的數組類型,那麼這個類型也就被最外層的 typeof 給解析到了,那麼最終的 pchar 也就是這個類型了,至關於 const char *pchar[4] = {...};
再來考驗一下你的指針,此次是函數指針:
// demo 05
int add(int param1, int param2) {
return param1 + param2;
}
int sub(int param1, int param2) {
return param1 - param2;
}
int mul(int param1, int param2) {
return param1 * param2;
}
int main() {
int (*func[3]) (int, int) = {add, sub, mul};
typeof(func[0](1, 1)) sum = 100;
typeof(func[1](1, 1)) dif = 101;
typeof(func[2](1, 1)) pro = 102;
printf("sum:\t%d\n", sum);
printf("dif:\t%d\n", dif);
printf("pro:\t%d\n", pro);
return 0;
}
這個 demo 中先定義了三個函數,這三個函數都是返回值爲 int 類型,而且接受兩個 int 型的參數,而後在 main 函數中定義了一個函數指針數組 func,並用上面三個函數名將其初始化,而後咱們來看底下的第一個 typeof 會推導出什麼類型,func[0] 就是指 add 這個函數,後面的括號裏跟了兩個參數,說白了就是簡單的 add(1, 1) 的調用,而 add 會返回一個 int 型值,因此最終推導出的類型就是 int 型,其它兩個都是同理,因此 sum、dif、pro 其實就是三個整型數,至關於 int sum = 100; int dif = 101; int pro = 102; 好吧,我認可這個 demo 有點坑,並且這個例子舉得不恰當,輸出結果就是它們分別的值:
咱們再看看它在宏定義中的應用:
// demo 06
#define pointer(T) typeof(T *)
#define array(T, N) typeof(T[N])
int main() {
array(pointer(char), 4) pchar = {"hello", "world", "good", "night"};
for (int i = 0; i < 4; i++)
printf("pchar[%d]:\t%s\n", i, pchar[i]);
return 0;
}
這裏用到了宏函數,pointer(T) 會被替換爲 typeof(T *),也就是說 pointer 後面跟某個類型的名字,通過預處理以後就會變成用 typeof 解析相應類型的指針類型,而 array 後面跟一個類型名和一個整數,而後 typeof 就會解析爲該類型的一個數組,這樣 main 函數中的 array(pointer(char), 4),pointer(char) 首先會被解析爲 char * 型,而後外層的 array 會再被解析爲包含 4 個 char * 元素的數組類型,因此就至關於 char *pchar[4] = {...}; 輸出結果以下:
好了,囉嗦了這麼多,typeof 這個關鍵字總算是知道用來幹什麼了吧,感受好像語法挺晦澀的,並且沒有什麼實際用途,那好吧,我再讓大夥看一看實際項目中的一個例子:
/*
* 選自 linux-2.6.7 內核源碼
* filename: linux-2.6.7/include/linux/kernel.h
*/
#define min(x,y) ({ \
typeof(x) _x = (x); \
typeof(y) _y = (y); \
(void) (&_x == &_y); \
_x < _y ? _x : _y; })
上面這個例子是選自 linux 2.6.7 內核中 include/linux/kernel.h 這個頭文件,宏定義 min 的做用是從兩個相同類型的對象中選取一個最小的,它接受兩個參數 x 和 y,後面的宏替換部分就用 typeof 定義兩個變量 _x 和 _y,並分別賦值爲 x y,這裏用 typeof 的做用就是可讓 min 接受任何類型的參數而沒必要侷限於某一個單一類型,這有點泛型編程的味道了,最後一個語句 _x < _y ? _x : _y; 用了一個條件運算符來返回兩者之中最小的,中間還有一句 (void) (&_x == &_y); 看起來好像是廢話,其實這句話是有特殊用意的,由於咱們不能保證你在使用 min 的時候傳入的兩個參數都是相同的類型,這時候就須要作一個檢測,而 C 語言不支持直接 typeof(_x) == typeof(_y) 這樣的操做,因此就取其地址,用指針類型來比較,若是兩個指針的類型不一致,編譯器就會產生警告以達到檢測的效果,至於前面的 (void),是由於僅表達式 &_x == &_y 自己是沒有意義的,若是沒有這個 (void) 編譯器一樣會警告:statement with no effect [-Wunused-value],無效的語句,若是不想看到這個警告,那就在前面加個 (void) 忽略掉。
typedef irqreturn_t (*irq_handler_t)(int,void*);
用typedef 定義了一個函數指針類型irq_handler_t,指向的函數原型返回類型爲 irqreturn_t 它接收的參數類型就是int 和void* 兩個參數