C語言結構體struct的語法解析
本節內容需要結合視訊講解才能更容易理解,視訊播放地址如下:
用java開發編譯器
本節,我們著重研究結構體定義,也就是struct 這種變數定義,C語言編譯器是如何解析的,本節我們要解析的結構體定義如下:
struct tag {
int x;
long y;
char z;
struct tag* p;
}name;
1.1 結構體定義的解析語法
* TYPE_SPECIFIER -> STRUCT_SPECIFIER * * STTUCT_SPECIFIER -> STRUCT OPT_TAG LC DEF_LIST RC * | STRUCT TAG * * OPT_TAG -> TAG * * TAG -> NAME * * DEF_LIST -> DEF * * DEF_LIST -> DEF_LIST DEF * * * * * * DEF -> SPECIFIERS DECL_LIST SEMI * | SPECIFIERS SEMI * * * DECL_LIST -> DECL * | DECL_LIST COMMA DECL * * DECL -> VAR_DECL * * VAR_DECL -> NEW_NAME * | VAR_DECL LP RP * | VAR_DECL LP VAR_LIST RP * | LP VAR_DECL RP * | START VAR_DECL *
我們先看這一句定義:
STTUCT_SPECIFIER -> STRUCT OPT_TAG LC DEF_LIST RC
STRUCT 是關鍵字struct 對應的標籤, OPT_TAG 對應的是結構體變數的名字tag, LC 對應左大括號, DEF_LIST 對應結構體內部變數的定義序列,RC就是右大括號。這一句語法就已經描述了整個結構體的定義,解析的終點其實在 DEF_LIST, 這個非終結符描述的是結構體內部變數的定義規則,對DEF_LIST的解析是整個解析過程的難點。
1.2語法解析流程描述
解析開始時,詞法解析器會把關鍵字struct讀入,並返回一個STRUCT標籤。
讀入struct 後面的變數名tag, 返回對應標籤NAME.
根據表示式 TAG -> NAME, 將NAME轉換成非終結符TAG.
通過表示式 OPT_TAG -> TAG , 將TAG轉換成非終結符OPT_TAG.
讀入左大括號{, 並返回對應標籤LC.
*讀入關鍵字int, 並返回對應的標籤TYPE
*根據表示式TYPE_SPECIFIER -> TYPE, 將TYPE替換成非終結符TYPE_SPECIFIER. 這個表示式在前面的章節中講解過,本節沒有列出來。
*根據表示式 TYPE_OR_CLASS -> TYPE_SPECIFIER 將非終結符TYPE_SPECIFIER 轉換成TYPE_OR_CLASS.
*通過表示式 SPECIFIER -> TYPE_OR_CLASS, 將非終結符TYPE_OR_CLASS轉換成SPECIFIERS.
上面幾步完成了對關鍵字int 的解析。
接著讀入變數名x, 返回對應的標籤NAME.
*根據表示式NEW_NAME -> NAME, 將非終結符NEW_NAME 壓入堆疊。
*根據表示式VAR_DECL -> NEW_NAME 將非終結符換成VAR_DECL.
*通過表示式DECL -> VAR_DECL 將非終結符DECL替換掉VAR_DECL.
*通過表示式DECL_LIST -> DECL 將非終結符DECL_LIST 壓入堆疊。
*讀入分號,並返回對應的標籤SEMI
*根據表示式DEF -> SPECIFIERS DECL_LIST SEMI 將解析堆疊上的符號全部彈出,換成非終結符DEF.
再次通過 DEF_LIST -> DEF 將非終結符壓入堆疊。
在這一步我們可以看到,”int x ; “, 這一個變數定義語句可以被非終結符DEF_LIST所描述。
接下來,解析器讀入語句 “long y ;”,該語句的解析也同樣經歷前面帶*號的解析步驟,最後”long y;” 會解析成非終結符DEF,此時解析堆疊頂部有兩個非終結符DEF, DEF_LIST.
通過表示式 DEF_LIST -> DEF_LIST DEF 將兩個非終結符歸納成一個非終結符DEF_LIST.
解析器繼續讀入 “char z;”, 同樣經歷帶*號的解析步驟後,把該語句解析成非終結符DEF, 這時解析堆疊頭部又在此含有兩個非終結符 DEF_LIST, DEF, 於是又可以通過表示式 DEF_LIST -> DEF_LIST DEF 將兩個非終結符歸納為一個非終結符DEF_LIST.
接下來解析器再次遇到關鍵字struct, 讀入後返回對應標籤STRUCT.
入讀struct 後面的變數名tag,返回對應標籤NAME.
運用表示式 TAG -> NAME, 將非終結符TAG壓入堆疊。
採用表示式STRUCT_SPECIFIER -> STRUCT TAG 將堆疊頂部的兩個非終結符替換成STRUCT_SPECIFIER.
再通過TYPE_SPECIFIER -> STRUCT_SPECIFIER 將棧頂非終結符替換成TYPE_SPECIFIER.
接著分別通過兩個表示式TYPE_OR_CLASS -> TYPE_SPECIFIER 和 SPECIFIERS -> TYPE_OR_CLASS, 將棧頂元素替換成SPECIFIERS.
然後把代表指標的* 讀入,返回對應標籤STAR.
讀入星號後面的變數名p, 返回對應的標籤NAME.
通過表示式NEW_NAME -> NAME, 將非終結符NEW_NAME壓入解析堆疊。
通過VAR_DECL -> NEW_NAME 將棧頂元素替換成VAR_DECL.
此時棧頂元素包含VAR_DECL, 和 STAR, 這兩個元素正好形成表示式:
VAR_DECL -> STAR VAR_DECL 的右邊部分,於是經過一次reduce,將堆疊頂部的兩個元素替換成VAR_DECL.
繼續通過表示式DECL -> VAR_DECL, 將非終結符DECL壓入堆疊。
DECL 可以通過表示式DECL_LIST -> DECL 替換成DECL_LIST.
接著讀入變數p後面的分號,返回對應標籤SEMI
此時,解析堆疊上含有三個元素:SEMI, DECL_LIST, SPECIFIERS, 他們正好構成表示式DEF -> SPECIFIERS DECL_LIST SEMI 的右邊,於是通過該表示式進行一次reduce, 將DEF替換掉這三個符號。
此時,堆疊頂部有兩個元素,DEF, DEF_LIST, 又正好構成表示式:
DEF_LIST -> DEF_LIST DEF 的右邊,於是又可以用DEF_LIST 替換掉堆疊頂部的兩個元素。
接著讀入右括號 }, 返回對應標籤RC.
這時,堆疊頂部的5個元素正好對應表示式:
STRUCT_SPECIFIER -> STRUCT OPT_TAG LC DEF_LIST RC 的右邊,於是解析器可以一下子將這5個元素全部替換成STRUCT_SPECIFIER.
接著解析器可以通過TYPE_SPECIFIER -> STRUCT_SPECIFIER 將TYPE_SPECIFIER壓入堆疊。
然後把}後面的變數名name,讀入,解析流程跟前面講解的流程一樣。
讀入最後的分號後,解析堆疊上的元素正好構成表示式:
EXT_DEF -> .OPT_SPECIFIERS EXT_DECL_LIST SEMI
的右邊部分,於是整個解析堆疊頂部的元素全部彈出,換成符號EXT_DEF.
接下來的推導跟以前一樣,經過一系列固定步驟後,全域性非終結符PROGRAM會被壓入堆疊,從而使得解析器接收輸入文字。
由此可見,依賴本節給出的語法定義,解析器能夠順利的分解結構體的程式碼。
通過這幾節的解析流程分析,我們可以看到,寫的再繁雜,再雜亂無章的程式程式碼,只要符合語法,那麼這些看似隨機組合的字元或單詞,本質上遵從著一種非常嚴謹的層次和結構,這種層次和結構可以通過語法定義的方式描述出來,大道至簡,任何複雜的系統,其本質都可以歸因為若干簡單的原理。這就是科學之美,編譯原理的演算法之美,學習編譯原理或任何科學知識,其實是一種享受美的過程。
要體驗這種系統邏輯之美,需要巨大的耐心,和不厭其煩的探索,持之以恆的意志力,有這種恆心的人,才有可能“會當凌絕頂,一覽眾山小”,學習是一個不斷攀爬,跌倒,再攀爬的過程,只有要緊牙,永不放棄的人,才有可能在光明頂感受到“蕩胸生層雲,決眥入歸鳥”的人生成就感。
再次以王安石《遊褒禪山記》為每一位願意“吾將上下而求索”的同學共勉:
“古人之觀於天地、山川、草木、蟲魚、鳥獸,往往有得,以其求思之深而無不在也。夫夷以近,則遊者眾;險以遠,則至者少。而世之奇偉、瑰怪,非常之觀,常在於險遠,而人之所罕至焉,故非有志者不能至也。有志矣,不隨以止也,然力不足者,亦不能至也。有志與力,而又不隨以怠,至於幽暗昏惑而無物以相之,亦不能至也。然力足以至焉,於人為可譏,而在己為有悔;盡吾志也而不能至者,可以無悔矣,其孰能譏之乎?此餘之所得也!”
相信我,你,並不孤獨!