GCC 編譯器對 C 語言標(biāo)準(zhǔn)進(jìn)行了一些列擴展,接下來會逐個介紹GNU C 的擴展語法,可能有很多我們習(xí)以為常的用法,亦或是大家不常用的操作。
本文介紹以下兩個擴展語法:
指定初始化
語句表達(dá)式的應(yīng)用
1. 指定初始化
在 C 語言標(biāo)準(zhǔn)中,當(dāng)我們定義并初始化一個數(shù)組時,常用方法如下:
?
?
int?a[10]?=?{0,1,2,3,4,5,6,7,8};
?
?
按照這種固定的順序,我們可以依次的對a[0] 到 a[8] 賦值。a[9] 沒有賦值,編譯器會自動設(shè)置為0.
當(dāng)數(shù)組畢竟小時,使用這種初始化方式會比較方便,但是當(dāng)數(shù)組比較大,并且數(shù)組里的非零元素不連續(xù),在按照固定順序賦值就很麻煩了。
C99 標(biāo)準(zhǔn)改進(jìn)了數(shù)組初始化方式,支持指定元素初始化,不在按照固定的順序初始化。
?
?
int?b[100]?=?{[10]?=?1,?[30]?=?2};
?
?
通過數(shù)組元素索引,我們可以直接給指定的數(shù)組元素賦值,除了數(shù)組,一個結(jié)構(gòu)體變量的初始化,也可以通過指定某個結(jié)構(gòu)體成員直接賦值。
在早期 C 語言標(biāo)準(zhǔn)不支持指定初始化時,GCC編譯器就已經(jīng)支持指定初始化了,因此這個特性也被看做GCC的一個擴展特性。
1.1 指定初始化數(shù)組元素
在 GNU C 中,通過數(shù)組元素索引,我們可以直接給指定的幾個元素賦值。這里注意,各個賦值之間用逗號隔開,而非分號
?
?
int?b[100]?=?{[10]?=?1,?[30]?=?2};
?
?
如果想給數(shù)組中一個索引范圍的元素初始化,可以采用?...
?
?
#include?int?main(void) { ????int?b[100]?=?{?[10?...?30]?=?1,?[50?...?60]?=?2?}; ????for?(int?i?=?0;?i?100;?i++)?{ ????????if?(i?%?10?==?0)?{ ???????????printf(" "); ????????}??? ????????printf("%d?",?b[i]); ????}??? ????printf(" "); ???? ????return?0; }
?
?
GNU C 支持 使用?...?表示范圍擴展,在這里使用[10 ... 30] 表示一個范圍,相當(dāng)于給b[10] 到 b[30] 之間的20個數(shù)賦值。
...?不僅可以用在數(shù)組初始化中,也可以用在switch-case 語句中。
?
?
int?main(void) { ????int?i?=?4; ????switch(i) ????{ ????case?1: ????????printf("1 "?); ????????break; ???? ????case?2?...?8: ????????printf("%d "?,?i); ????????break; ???? ????default: ????????printf("default "); ????????break; ????} ????return?0; }
?
?
這里需要注意?...?兩邊的數(shù)據(jù)之間要有空格,否則,會報編譯錯誤。
1.2 指定初始化結(jié)構(gòu)體成員
和數(shù)組類似,在 C 語言標(biāo)準(zhǔn)中,初始化結(jié)構(gòu)體變量也要按照固定順序,但是在 GNU C 中我們可以結(jié)構(gòu)體域來指定初始化某個成員。
?
?
struct?student?{ ????char?name[20]; ????int?age; }; int?main(void) { ????struct?student?stu1?=?{"s1",?20}; ????printf("%s:%d ",?stu1.name,?stu1.age); ???? ????struct?student?stu2?=?{ ????????.name?=?"s2"; ????????.age?=?28; ????} ???? ??????printf("%s:%d ",?stu2.name,?stu2.age); ?????? ??????return?0; }
?
?
1.3 Linux 內(nèi)核中的指定初始化
在Linux ?驅(qū)動中,大量使用 GNU C的這種指定初始化方式,通過結(jié)構(gòu)體成員來初始化結(jié)構(gòu)體變量:
?
?
static?const?struct?file_operations?ci_port_test_fops?=?{ ?.open??=?ci_port_test_open, ?.write??=?ci_port_test_write, ?.read??=?seq_read, ?.llseek??=?seq_lseek, ?.release?=?single_release, };
?
?
在驅(qū)動程序中,我們經(jīng)常使用file_operations 這個結(jié)構(gòu)體來注冊我們開發(fā)的驅(qū)動,然后系統(tǒng)會以回調(diào)的方式。
結(jié)構(gòu)體file_operations 里定義了很多結(jié)構(gòu)體成員,而在這個驅(qū)動中,我們只是初始化了部分成員變量。通過訪問結(jié)構(gòu)體的各個成員域來指定初始化,當(dāng)結(jié)構(gòu)體成員很多時優(yōu)勢就體現(xiàn)出來了,初始化會更加方便。
1.4 指定初始化的好處
指定初始化不僅使用靈活,還有一個好處就是代碼易于維護(hù)。特別是在Linux 內(nèi)核這種大型項目中,有幾萬個文件,大量使用了這種指定初始化。
如果采用標(biāo)準(zhǔn)C語言按照固定順序初始化賦值,一旦增加、刪除一個成員,大量的文件都有重新調(diào)整初始化順序,牽一發(fā)而動全身。
2. 語句表達(dá)式
2.1 語句表達(dá)式
GNU C 對 C 語言標(biāo)準(zhǔn)作了擴展,允許在一個表達(dá)式里內(nèi)嵌語句,允許在表達(dá)式內(nèi)部使用局部變量、for 循環(huán) 和 goto 跳轉(zhuǎn)語句。這種類型的表達(dá)式,我們稱之為語句表達(dá)式:
?
?
(?{?表達(dá)式1;?表達(dá)式2;?表達(dá)式3;?}?)
?
?
語句表達(dá)式最外面使用 () 括起來,里面使用 {} 包起來的是代碼塊,代碼塊里允許內(nèi)嵌各種語句。
語句的格式可以是一般表達(dá)式,也可以是循環(huán)和跳轉(zhuǎn)語句。
和一般表達(dá)式一樣,語句表達(dá)式也有自己的值。語句表達(dá)式的值為內(nèi)嵌語句中最后一個表達(dá)式的值。
?
?
int?main?(void)?{ ????int?sum?=?0; ????sum?=?(? ????????{????????????????????????????????????????? ????????????int?s?=?0,?i?=?0;? ????????????for?(i?=?0;?i?10;?i++)? ????????????????s?=?s?+?i;? ????????????s;?? ???????});? ????printf("sum?=?%d? ",?sum); ????return?0; }
?
?
編譯:
?
?
gcc?-std=gnu89 gnu1.c?/?gcc?-std=gnu99 gnu1.c
?
?
在上面的程序中,通過語句表達(dá)式計算1到10的累加,因為語句表達(dá)式的值等于最后一個表達(dá)式的值,所以在 for 循環(huán)后面要添加一個s。如果你將這個值改成 s=100,會發(fā)現(xiàn)sum結(jié)果變成100了。
在語句表達(dá)式中使用跳轉(zhuǎn)。
?
?
int?main?(void)?{ ????int?sum?=?0; ????sum?=?(? ????????{????????????????????????????????????????? ????????????int?s?=?0,?i?=?0;? ????????????for?(i?=?0;?i?10;?i++)? ????????????????s?=?s?+?i;? ????????????goto?here; ????????????s;?? ???????});? ????printf("sum?=?%d? ",?sum); here: ????printf("here: "); ????printf("sum?=?%d? ",?sum); ????return?0; }
?
?
2.2 在宏定義中使用語句表達(dá)式
語句表達(dá)式的主要用途在于定義功能復(fù)雜的宏。使用語句表達(dá)式來定義宏,不僅可以實現(xiàn)復(fù)雜的功能,還能避免宏定義帶來的歧義和漏洞。
下面就以一個例子,讓我們領(lǐng)略宏定義的殺傷力。
題目:定義一個宏,求兩個數(shù)的最大值。
合格:
對于學(xué)過C語言的同學(xué),寫出這個宏基本上不是什么難事,使用條件運算符即可完成。
?
?
#include?#define?MAX(x,?y)????x?>?y???x?:y int?main() { ????printf("max?=?%d ",?MAX(1,2)); ????printf("max?=?%d ",?MAX(2,1)); ????printf("max?=?%d ",?MAX(2,2)); ????printf("max?=?%d ",?MAX(1!=1,1!=2)); ???? ????return?0; }
?
?
運行結(jié)果如下,發(fā)現(xiàn)最后一個結(jié)果與預(yù)期不符合
?
?
max?=?2 max?=?2 max?=?2 max?=?0?
?
?
我們使用預(yù)處理命令展開宏
?
?
gcc?-E gnu1.c?-o gnu1.i
?
?
因為 ?> 號的優(yōu)先級(6)大于 != 號的優(yōu)先級,所以展開后,結(jié)果就和預(yù)期不一樣了。
為了避免這種錯誤,我們可以給宏參數(shù)加一個小括號,防止展開后的運算符發(fā)生變化。
?
?
#define?MAX(x,?y)????(x)?>?(y)???(x)?:(y)
?
?
中等:
上面的宏只能算合格,還是存在漏洞:
?
?
#include?#define?MAX(x,?y)????(x)?>?(y)???(x)?:(y) int?main() { ???printf("max?=?%d ",?3?+?MAX(1,2));? ??? ???return?0; }
?
?
預(yù)期結(jié)果應(yīng)該是5,結(jié)果是1.
預(yù)處理展開如下:
優(yōu)先級順序:+?大于?>?號 所以表達(dá)式變成了
?
?
4?>?2???1:2
?
?
故對此宏進(jìn)行改進(jìn):
?
?
#define?MAX(x,?y)?(?(x)?>?(y)???(x)?:?(y)?)
?
?
使用小括號括起來,就避免了當(dāng)一個表達(dá)式同時含有宏定義和其他高優(yōu)先級運算符時破壞整個表達(dá)式的運算順序。
良好:
上面的宏,雖然解決了運算符優(yōu)先級問題,然任然存在一些漏洞。
定義兩個變量i和j,然后比較兩個變量的大小,并做自增運算。實際運行結(jié)果發(fā)現(xiàn)max=7
?
?
#include?#define?MAX(x,?y)????(?(x)?>?(y)???(x)?:(y)?) int?main() { ????int?i?=?2; ????int?j?=?6; ???? ????printf("max?=?%d ",?MAX(i++,j++)); ???? ????return?0; }
?
?
預(yù)處理展開后表達(dá)式如下:
i 和 j 在展開后做了兩次自增運算,導(dǎo)致打印 max的值為7。
當(dāng)然,在C語言編程規(guī)范里,使用宏時一般是不允許參數(shù)變化的。但是萬一碰到這種情況,又該如何處理呢?
這個時候,語句表達(dá)式就需要上場了,在語句表達(dá)式中定義兩個臨時變量,分別來暫時存儲 i 和 j 的值,然后用臨時變量進(jìn)行比較。
?
?
#include?#define?MAX(x,?y)????({? ????int _x?=?x;? ????int _y?=?y;? ????_x?>?_y???_x?:?_y;? ????}) int?main() { ????int?i?=?2; ????int?j?=?6; ????printf("max?=?%d ",?MAX(i++,j++)); ???? ????return?0; }
?
?
預(yù)處理展開:
優(yōu)秀:
在上面定義的宏中,我們定義了兩個int型變量,只能比較整型數(shù)據(jù)。如果希望比較其他數(shù)據(jù)類型呢?
?
?
#include?#define?MAX(type,?x,?y)????({? ????type _x?=?x;? ????type _y?=?y;? ????_x?>?_y???_x?:?_y;? }) ???? int?main() { ????int?i?=?2; ????int?j?=?6; ????printf("max?=?%d ",?MAX(int,?i++,j++)); ????printf("max?=?%f ",?MAX(float,?3.14,3.15)); ???? ????return?0; }
?
?
很容易想到通過一個參數(shù),將數(shù)據(jù)類型傳進(jìn)去。
進(jìn)一步修改:
我們只想保留兩個參數(shù)。
?
?
#include?#define?MAX(x,?y)????({? ????typeof(x)?_x?=?(x);? ????typeof(y)?_y?=?(y);? ????(void)?(&_x?==?&_y);? ????_x?>?_y???_x?:?_y;? }) int?main() { ????int?i?=?2; ????int?j?=?6; ???? ????printf("max?=?%d ",?MAX(i++,j++)); ????printf("max?=?%f ",?MAX(3.14,3.15)); ????return?0; }
?
?
GNU C 使用關(guān)鍵字 typeof 來獲取宏參數(shù)的數(shù)據(jù)類型。比較難以理解的就是第三句:(void) (&_x == &_y);
這句話看起來多余,實際上有兩個作用:
對于不同類型的指針比較,編譯器會發(fā)生一個警告,提示兩個數(shù)據(jù)類似不同;
當(dāng)比較結(jié)果沒有用到時,有些編譯器可能會給一個警告,加上(void) 后 可以消除警告。
3. 總結(jié)
本文主要介紹了GNU C 的擴展:指定初始化和語句表達(dá)式的使用,重點介紹了語句表達(dá)式在宏中的使用。
事實上 Linux 內(nèi)核大量使用了GNU C 的擴展語法,特別是語語句表達(dá)式在宏中的使用,了解GNU的擴展,有助于我們對C語言的認(rèn)識更加清晰。
編輯:黃飛
?
評論