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