日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

语法分析与中间代码生成

發布時間:2025/3/15 编程问答 14 豆豆
生活随笔 收集整理的這篇文章主要介紹了 语法分析与中间代码生成 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

??這章和上一章(屬性文法和語法制導翻譯)是緊密聯系的,共同完成了編譯過程的第三步——語義分析與中間代碼產生。

??開篇先解釋兩個問題:
????1. 什么是語義分析?它和之前幾部分有什么不同呢?
????2. 什么是中間代碼?為什么需要產生中間代碼?

??問題一:什么是語義分析?它和之前幾部分有什么不同呢?

??詞法分析主要完成的是標識符/算符等等定義是否符合規定。語法分析是看程序的結構是否符合文法的要求,也就是下一部分接這個對不對?語義分析就上升到概念層了,我以C語言舉個例子:使用一個變量之前必須對這個變量進行定義,否則就會出現undefine的bug,檢查這部分的就是語義分析。再有就是break語句必須要定義在循環體中,如果沒有循環而有break,肯定是要報錯的呀~還有上下無關文法無法描述整個程序語言就是因為其無法表示語義的相關分析。

??問題二:什么是中間代碼?為什么需要產生中間代碼?

??編程時寫的叫做高級語言,機器運行的叫做機器語言。中間代碼可以理解成一個過渡。其實匯編語言也是一種過渡,但中間代碼主要強調的指明過程中數據和地址及相關操作。為什么需要產生中間代碼,是因為中間代碼有如下幾點好處

??看完了前面的內容,希望你對于語義分析和中間代碼有一個簡單的了解。再繼續看下面的內容。先說一下本文敘述的結構吧,大體可以分為四個部分:
??1. 中間代碼的表達形式(大致3種)
??2. 說明語句的翻譯
??3. 賦值語句的翻譯
??4. 布爾表達式/控制語句的翻譯

一、 中間代碼的表達形式

??中間代碼的表達形式從大的角度上分可分為3種:后綴式、圖表示法、三地址代碼,下面簡單介紹(其實主要用的還是三地址代碼,前兩種作為擴充知識提一下吧)

??后綴式

??后綴式也叫逆波蘭式,是一種表達式的方法。形式化定義如下:

??舉個例子:

??后綴式看上去有點奇怪,反倒是例子中左邊的讓人更直觀理解。其實左邊叫做中綴式,還有一種叫做前綴式(也叫做波蘭式)。我們平常寫的都是中綴式,但計算機識別字符串從左到右或者從右到左,無法完成中綴式的計算規則。所以在計算機里面輸入中綴式,一般也會先轉化成后綴式。對于后綴式的計算就需要用到數據結構中棧的相關內容啦!

??圖表示法

??這里的圖前面介紹過,就是抽象語法樹。不過新增的一部分是DAG圖。啥是DAG圖呢?其實就是將抽象語法樹中公共因子凝聚成一個節點。舉個例子就過:

??三地址代碼

??三地址代碼是由兩個運算操作數地址,一個結果操作數地址組成的。一般形式是z = x op y(op代表運算符,其實和高級語言還是挺像的)。對于三地址代碼,有如下這幾種類型:

??三地址代碼還有三種表達形式:四元式、三元式、間接三元式。因為后續內容只涉及到四元式,老師課程上也只講了這一種。我就只介紹這一種了,剩下的如果感興趣可以自己網上搜一搜:
??四元式包含的四部分是兩個源操作數,一個目的操作數再加上一個操作符,對于運算的轉化形式如下:

??四元式有一個明顯的缺點:會引入大量的臨時變量(像上表中的T1,T2,T3,T4,T5)但這個問題在后續的優化過程會得到解決。

二、 說明語句的翻譯

??首先,什么是說明語句呢?就有點兒像變量定義的語句。那這步主要干嘛呢?提一個之前的概念:符號表(我都快忘了~)在詞法分析的過程中,當我們識別到一個標識符的時候,會放在符號表中。除了標識符的名字外還有一些其他的屬性,比如類型,在內存中存儲位置等等。
??先介紹兩個概念:

??作用域

??每一個函數調用都會有屬于它本身的作用域,每一個函數都在維護一個符號表。它們就是當前標識符的有效范圍,當兩個函數進行嵌套并且標識符名相同時,內層作用域屬于里面定義的標識符。(這個很容易理解,就是C語言中的變量覆蓋)

??符號表:

??制作符號表用到了下面幾個函數:

??如果把符號表和翻譯模式加在一起,舉個例子如下所示:

??關于符號表有些信息:

??舉個例子,看看函數過程中的符號表是什么樣子的:

??從上面可以看出,符號表表頭中維護的就是各個指針。能夠在指定的位置進行跳轉進行操作。

三、 賦值語句的翻譯

??賦值語句就不用多說了,在大多數語句中賦值之前需要先進行定義。所以在賦值時查詢符號表是否進行定義就是翻譯的操作之一。在翻譯過程中會有lookup(id.name)這樣的函數完成檢驗操作。
??舉個賦值語句加翻譯模式的例子:

??對于賦值語句和說明語句內容比較少,課程主要的內容在后面兩個,下面繼續開始!

四、布爾表達式的翻譯/控制流語句的翻譯

??布爾表達式:用布爾運算符號(and or not)作用到布爾變量或關系表達式上而組成
??布爾表達式一方面可以用來進行邏輯計算,另一方面可用作判斷條件。
??當進行布爾運算時,存在一種優化的思想。大體是這樣的對于or來說,如果你已知第一個操作數是正確的,那第二個操作數就不重要了,結果一定是true。相反,若第一個錯誤,則要看看第二個語句的情況。True和false的情況語句不會完全相鄰,就需要一些跳轉,自然就要生成一些標號。下面我列舉下布爾表達式進行計算時的屬性文法:


??那如果是控制流語句呢?比如if,if-else,while語句中的判斷條件。那對布爾表達式的翻譯都需要干些啥呢?很明顯就是要知道在各種情況下指令之間是怎么跳轉的。對于布爾表達式的翻譯內容主要可以分為兩種情況:屬性文法和翻譯模式。
??屬性文法就是先定義語法動作再定義語法規則。寫一個程序的屬性文法是有套路的。Let me show you! 主要的方法是圖解法,因為常用在循環中,我就以循環舉例。對于單步的條件判斷,可同理得到。
??有產生式S->for(E1;E2;E3)S1,寫其屬性文法
??先看看結果,如圖所示:

??那為啥會有這個結果呢?首先我們需要先畫一個圖從上到下按照產生式的順序為E1,E2,E3,S1,四段代表代碼段。就像這樣:

??先來分析一下這個循環的執行過程:初始化E1,接著對E2進行判斷,滿足E2跳轉S1,不滿足E2跳出循環(即S),S1執行過后(或者S1中的跳轉)跳轉E3執行自增,E3執行跳轉E2再次判斷,就這樣循環,循環…
??從上面一個簡單的過程中可以了解到,有跳轉的過程,其中包含if的跳轉也包含直接的跳轉。但無論哪種,都要有一個跳轉的目標,也就是說要為每一個目的地建立一個標記。有一個通用的套路:為if語句建立E.true和E.false兩個屬性。為每一個終點建立一個標號。結合上面的例題,E2跳轉S1——E2是if語句,滿足條件跳轉,建立一個標號,但因為也是E2.true, 之前建立過了,不需要再建了。S1跳轉E3——無條件跳轉,生成一個E3.label。E3跳轉E2——無條件跳轉,生成一個E2.label。把他們標在上面的圖里就是這樣:

??我們再把goto語句寫到代碼段中,就像這樣:

??以上過程做好了前期準備,可以開始從上到下寫答案了:遵循三個步驟:1.創建標號。2.連鏈。3.連接代碼

??1. 創建標號:上面過程中我們創建了3個標號,就把它們的名字寫到左邊,右邊newlabel。例如E2.true = newlabel。

??2. 連鏈主要是進行屬性間的串聯,對于E2如果不滿足就要跳出去到S.next。那么就是E2.false = S.next。E3.label = S1.next(注意對于E來說只有true和false兩個屬性,不要再想E.next了哈!)

??3. 連接代碼的過程就比較直觀了,連接使用||(雙豎線),按照圖來,碰到代碼塊就寫上xx.code。碰到標號就gen(‘xxx’,’:’),碰到goto就gen(‘goto’, ‘xxx’)。里面的xxx就是標號嗎比如E2.true,E3.label這種

??經過以上的過程,相信你已經會寫屬性文法了。那翻譯模式到底怎么寫呢?二者有什么不同嗎?且聽我慢慢道來!
??還記得翻譯模式的主要工作嗎?在產生式中插入動作,完成屬性的計算。在前面介紹過“遍”的概念,我們追求的應該是一遍完成所有功能。但在控制流語句中,涉及到語句間的跳轉。如果在掃描時,還不知道具體的跳轉位置怎么辦呢?為了完美的完成這個任務,就需要一項技術——回填。
??回填的定義如下圖:

??下面列舉一個回填與布爾表達式判斷的例子:

??例子中包含三個布爾表達式,對于布爾表達式實際隱含if的跳轉語句。同樣用兩個綜合屬性E.t和E.f去鏈接條件表達式為“真”的所有跳轉語句和為“假”所有跳轉語句。但在從左往右的執行過程中,對于or操作符來說,前面的false會跳轉到后面的起始位置。但目前還不知道后面的起始位置在哪里,但我們cd和ef規約之后就可以知道這個起始位置,到了那個時候再通過鏈表回填即可。
??同樣再對產生式S->for(E1;E2;E3)S1,寫其翻譯模式
??結果直接貼圖了:


??我們來分析一下這個結果是怎么來的,這就還需要用到上面那個圖:
??從圖中可以看出有跳轉,有標號。跳轉的目的地一般是標號,在E2,E3,S1之前都有我們新建立的標號。按照之前翻譯模式的構造方法,以M,N構造標號。即寫出M->ε{M.quad=nextquad}這樣的語義動作。再看下goto語句,總共有三處跳轉,其中一個是布爾跳轉,另外兩個是goto跳轉。前文提到跳轉需要回填技術。在進行回填時,以M2.next去回填s1.nextlist(goto跳轉)。以N.next去回填E2.truelist(if跳轉),對于E2.falselist沒有明確的回填目標。但已知觸發E2.falselist會跳出循環,執行S后的語句,即S.nextlist。所以將S.nextlist與E2.falselist進行綁定。出現關于S.nextlist的賦值語句。下面還剩下一個goto語句,即E3后面的goto語句,目標地址E2(標號為M1)。對于goto語句總會有nextlist這個屬性,從表達式和圖中的對應位置來看,goto語句對應于N標記符號。說明它不僅承擔標記的作用,還要完成跳轉。所以把nextlist屬性放到N上。按照回填的思路,用M1去回填N.nextlist。當寫N的產生式時,一方面要提供標號即N.quad=nextquad,也要指定跳轉,N.nextlist相當于我們自己加入的所以需要創建一個鏈表,即makelist。下面是跳轉指令j,目標地址不確定。(可能這里會有人有疑惑,我們都知道N跳轉的地址是M1,后面馬上就要回填,這里怎么還能不知道跳到哪里呢?原因是這樣的語句從左到右執行,回填語句發生在末尾。對于N來說,是一個獨立的產生式,所有的語義動作都發生在N完全定義后。N只知道它前面的一些信息,而不知道后面的語義動作是如何規范它的。這也是我們留存一個鏈表的原因)。別忘記S1之后也有goto語句,emit產生一句即可。按照翻譯模式的順序,先emit動作再回填再連鏈,就完成了這個循環體的翻譯模式書寫。
??通過以上的舉例,應該可以明白屬性文法和翻譯模式怎么寫了吧,這就是語義分析的整個過程。

??存在這樣一種情況,函數調用了另一個函數,或者調用結束后需要返回,那需要做些什么呢?


??對于第七章語義分析和中間代碼產生寫的時間非常長,不知從何下筆。究其原因,是對于屬性文法和翻譯模式的理解不到位(哦,第六章白寫了!)同時對于布爾表達式的理解也有問題。
??現在回過頭來看屬性文法和翻譯模式有了不一樣的感覺。兩者都是語義分析的方式,旨在賦予符號意義的同時分析意義是否符合我們的要求。翻譯模式作為特定的一種屬性文法,其表現形式有很多,諸如三地址代碼形式、抽象語法樹形式。以自下而上的規約過程為基礎?;靥罴夹g的添加是為了滿足一遍翻譯的需求。
??慚愧的是,在寫的時候,腦中大多是一些解題方法,而對于執行過程并沒有一個想象的動態圖。看來是理解不夠,后續仍要繼續加強完善!

感謝編譯原理課程謝老師對本文的認真修改,由于作者水平有限,如有錯誤之處請在下方評論區指正,謝謝!

總結

以上是生活随笔為你收集整理的语法分析与中间代码生成的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。