在進行靜態(tài)強類型語言的設(shè)計過程中,是一定需要提供一種語法實現(xiàn)變量和類型的關(guān)聯(lián)。這種語法一般稱之為 “聲明” 或者 “定義”。
如果用一種人類易讀的方式進行表達,可以寫作 define variable_name as type_name。沒有錯,你看到的這種形式類似于VB當(dāng)中 Dim As的方法。在語法分析的過程中,這種方法的好處很明顯。define 作為提示字,意味著一個聲明或者定義的開始,as 則代表后續(xù)的Tokens都是用于一個類型的,這樣關(guān)鍵字可以很顯著的將語義區(qū)分開。
但是這種寫法并不方便。C 一類的語言,都選用一種 prefix declaration specifier 的方式,也就是我們常常見到的:type_name variable_name 這樣的形式。舉個最簡單的例子,int i;。如果有類型修飾,可以作為一個基本類型的前綴或者后綴出現(xiàn)。例如const int i; 或者 int const i; 至于修飾含義是左綁定還是右綁定,這個需要取決于語言本身的設(shè)計。
如果事情是這個樣子的話,也就沒什么好說的了。但是在C語言里面,有兩個例外:函數(shù)和數(shù)組。例如,在C里面定義一個數(shù)組,寫作:const int array[expr]; 這種寫法既不是前綴寫法,也不是后綴寫法。類型被變量兩分了。如果僅僅是一個數(shù)組定義,那也好辦,當(dāng)做特殊情況處理就好了。
但是有時候我們的數(shù)組元素會變得非常復(fù)雜。 舉個例子,( struct {...} const [ constant ] identifier ( params... ) ) [ expr ]。你能理解這樣一個復(fù)雜的定義其實是一個函數(shù)的數(shù)組么?不僅僅是你不能,我想在撰寫語法規(guī)則的時候,又困難,又不合邏輯。
在C的EBNF中,這個問題解決起來也很復(fù)雜。
在這里,我們簡化一下C的語法,不考慮C的指針,不考慮變量初始化,不考慮類型修飾符,也不考慮一個類型定義多個變量的情況。
這就意味著你只能寫 int i; int j; j = 0; 而不能寫 int i, j = 0;
declaration ::= declaration_specifier declarator
declarator ::= identifier
| delcarator '[' expr ']'
| declarator '(' parameters_declaration_list ')'
| '(' declarator ')'
那么我問你,declarator是個什么東西。變量名?不是。函數(shù)聲明?也不是。數(shù)組?也不是。declaration_specifier呢?變量類型?是。數(shù)組元素類型?是。函數(shù)返回值類型?也是。顯然這樣一個語法要素具備了太多的語義。更重要的是,沒到最后,你是沒法確定declaration_specifier究竟是一個什么含義,identifier所代表的,究竟是個什么東西。顯然只有在聲明匹配完成之后,還需要進行復(fù)雜的推導(dǎo)過程,才能確定變量的嵌套結(jié)構(gòu)。
這個問題還導(dǎo)致了C語言里面的這么一個特性:那就是很出名的指針符號*的變量綁定性質(zhì)。在C語言中,*,[],() 操作符并沒有理解為對類型的修飾,而是理解為對變量的修飾。這就讓我們必須要這么寫: int m[expr], n[expr]; int m( int, int ), n( int, float );
而對變量,寫法就成了: int x, y; 這導(dǎo)致了同樣的寫法兩者在語義上的不一致性。這也是為什么新手云里霧里的根本原因了。按照普通變量的規(guī)矩,函數(shù)應(yīng)該寫成 int (x, y) (int);這樣的結(jié)構(gòu)。好吧,這樣還挑戰(zhàn)不倒你。但是如果我更復(fù)雜一點,加上初始化呢?就變成了 int ( x = p0, y ) (int) 這樣的結(jié)構(gòu),呃。
為什么C會這么做?難道完全的前置類型會讓語法分析工作變難么?很顯然不會。雖然C語言如此聲明的出發(fā)點不可考,但是想讓普通類型和數(shù)組聲明維持同樣的語法結(jié)構(gòu)是很簡單的事情,C#就給了一個很好的答案。在C#中,變量是如此聲明的:
declaration ::= declaration_specifier declarator
declaration_specifier ::= function_specifier | array_specifier | identifier
| '(' declaration_specifier ')'
function_specifier ::= declaration_specifier '(' parameter_declaration_list ')'
array_specifier ::= declaration_specifier '[' expression_list ']'
這樣,declaration_specifier完全就變成了類型,而declarator部分就和類型脫鉤了。在這種聲明方式中,我們就這么定義一個變量 int [] x, y, z; OK,這樣大家就理解了,x,y,z都是一個數(shù)組。而不會造成C語言當(dāng)中的誤解。
最后討論一下類型修飾的問題。這里只討論單一類型的類型修飾,下面我們會看到,復(fù)合類型其實也是一樣的。我們將類型聲明和表達式進行類比,就可以將類型理解為變量,類型修飾理解為單目操作符。
const type <==類比==> ~var
const就相當(dāng)于按位取反操作符~,type就相當(dāng)于var。const type這個表達式的結(jié)果就是一個常量化的type。那么,我們可以更廣泛的將聲明理解成 type_expression variable_name 這樣的形式。type_expression在語義分析的時候進行類型演算得出結(jié)果,variable_name則利用類型表達式獲得真正的類型,并實例化。下面我們寫出type_expression的演算語法:
type_expression ::= type_identifier
| type_op type_expression /* prefix style */
| type_expression type_op /* postfix style */
type_op ::= type_qualifier
當(dāng)然,對于 const int volatile 這樣的聲明,這個表達式還有二義性。這個二義性可以通過一定的方法消除,這一點一般的編譯原理教材都有詳細的討論。并且,我們完全可以將()和[]也納入到type_operator中,這兩個操作一個可以構(gòu)造出函數(shù)類型,一個構(gòu)造出數(shù)組類型來,這樣類型問題就得到了一個遞歸一致的解決。
當(dāng)然,更重要的是,我將type_expression variable_name這樣的聲明式變成typedef type_expression variable_name呢?
哈哈。
最后嚴重感謝一下VCZH,因為這小子吃了足夠的shit,我就可以不用繼續(xù)吃shit了。正所謂前人栽樹后人乘涼。