最近一直做C編譯器相關的開發(fā)十籍,感覺該總結一下。以前一直以為對C已經(jīng)足夠熟悉了剃法,結果被它奇葩的語法樹震驚了碎捺。碰巧最近心血來潮,想把一個GNU的僵尸項目jamvm救活改造一下贷洲,又發(fā)現(xiàn)了GCC的一些奇葩C語言擴展收厨。
聲明
C語言的聲明足夠奇怪,以至于丑魚書用了一章來解釋這個問題优构。對于一般的變量聲明诵叁,C語言采用的語法一般是T var
的形式,T表示變量的類型钦椭,var表示變量的名字拧额。但對于數(shù)組的聲明,如果要聲明一個大小為10的int數(shù)組a彪腔,C語言需要
int a[10]
而不是
int[10] a
而許多其他的語言采用的往往是類似后者的方式侥锦,比如Java,C#德挣,Go等等恭垦。
函數(shù)的聲明同樣的問題。比如這樣的一個函數(shù)
int foo(int a, int b)
{
return a + b;
}
它的類型可以表示為int ()(int, int)
, 其實一定有人發(fā)現(xiàn)了,如果對foo函數(shù)做前向引用的聲明番挺,我們會這么寫:
int foo(int, int);
而不是
int ()(int, int) foo;
函數(shù)指針也是同樣的問題唠帝,對于foo函數(shù)的指針,類型可以表示為int (*)(int, int)
建芙,但聲明函數(shù)指針時没隘,我們只能
int (*f)(int, int)
即聲明了一個變量f,類型為指針類型禁荸,指向目標的類型為int ()(int, int)
右蒲。如果想用T var
的形式聲明函數(shù)指針,只能曲線救國赶熟,利用typedef瑰妄。
C語言之所以把聲明形式搞得這么復雜,原因或許是為了追求變量的定義和使用盡量寫法上保持一致映砖。怎么樣间坐,是不是很奇怪?除了引入復雜性邑退,本身完全不一樣的兩個概念非要寫的一樣有何用竹宋?比如這個聲明是啥意思?
char* const (next)()
左值
先說結論。C99標準明確說明了Cast不能作為左值地技,所以現(xiàn)在的編譯器(gcc4.x或者clang3.x)遇到這種情況都會complain蜈七。但是老版本gcc居然有一個擴展,名曰casts-as-lvalue莫矗。
左值可以簡單理解為允許被賦值的值飒硅,可以被放在賦值號(=)左邊的值。那什么樣的值可以放在賦值號左邊作谚?權威的解答需要參考ISO/IEC 9899:1999三娩。為了方便我用CIL對左值的定義舉例
lval =
| Mem of exp
| Var of varinfo</pre>
可以看到左值如果是一個表達式,那么一定只一個訪存操作妹懒。比如*(ptr+1) = 1
雀监,顯然這里的ptr是一個指針類型。在編譯jamvm1.0版本源碼的時候眨唬,出現(xiàn)了大量的lvalue required as left operand of assignment錯誤会前。這里錯誤大部分都長的這個樣*((long long*)ptr)++ = 1
。我根據(jù)代碼上下文理解单绑,它是想根據(jù)強制類型轉(zhuǎn)換后的類型進行指針自增操作回官。不過經(jīng)過我調(diào)研,發(fā)現(xiàn)現(xiàn)在的編譯器已經(jīng)不支持一行代碼實現(xiàn)類似這樣語義的操作了搂橙。如果想要編譯含有這樣語句的C代碼歉提,可以嘗試使用gcc3.3.6笛坦。gcc3.3.6支持cast-as-lvalue擴展,但是本身并不含有cast-as-lvalue的代碼苔巨。
undefined behaviour
只舉個例子版扩。比如*p++ = p[-1]
,賦值號兩邊的計算順序不同編譯器是不同的侄泽,C標準對它沒有做嚴格的要求礁芦。所以這種寫法在開發(fā)中一定要避免,clang默認會拋一個warning悼尾,gcc不加-Wall
參數(shù)不會有任何提示柿扣。
總結
- 不要為了少寫一兩行代碼而是用一些非標準的extenstion或者trick,得不償失闺魏。
- Treat all warning as error 未状!
對JVM感興趣的話,jamvm1.0的確是個不錯的起點析桥,不到7000行的C司草,實現(xiàn)了一個java虛擬機該有的幾乎所有功能。問題是現(xiàn)在的主流編譯器都無法編譯它了泡仗。neojam是對jamvm1.0代碼修改后可以用gcc4.x編譯的版本埋虹,鏈接先放在這,相關文檔補全后會public娩怎。