龍書8章學習總結,略過了一些算法前端
源程序經過預處理器處理到編譯器造成目標彙編程序到彙編器造成可重定位機器代碼經過連接器/加載器變成目標機器代碼程序員
預處理器:展開全部宏定義,處理含有#部分的代碼,還有刪除全部的註釋//和/* */。算法
編譯:進行代碼的優化還有符號的彙總(符號表)以及語義分析 語法分析 詞義分析及優化後生成相應的彙編代碼文件。編程
彙編:將指令轉成當地操做系統的機器碼;生成可重定向的目標文件:x86體系下的.obj 和unix下的.o文件後端
連接過程:數組
將第一個步驟所生成的.o文件裏的段整合在一塊兒,進行【符號解析】,符號解析指的是將每一個符號引用恰好和一個符號定義聯繫起來。對符號表裏的未定義的符號找到其定義的地方,代碼段中,對全部指令未有其定義的符號都將其變爲正確的地址例如extern行的代碼以及main函數中的printf。解析正確後,再進行【符號重定位】,對其分配相應的虛擬地址。全部符號都擁有其正確的虛擬地址後,而後生成最終可執行的.exe文件,其運行的時候只須要代碼段和數據段。數據結構
分析代碼的語法語義的構成,收集有關信息放入符號表,前端閉包
根據中間表示和符號表中的信息構造目標程序,後端函數
會根據字符流生成詞素序列,並將有關信息放入符號表條目學習
構造相應的語法數
根據語法樹和符號表中的信息來檢查源程序是否和語言定義的語義一致也會收集類型信息以便中間代碼生成
生成一種可以被輕鬆翻譯成爲目標機器上的語言
機器無關的代碼優化改進中間代碼,以便生成更好的目標代碼
以源程序的中間表示形式做爲輸入映射到目標語言
記錄源程序中使用的變量的名字,並收集和每一個名字的各類屬性相關的信息
前端步驟1-4位一趟,代碼優化爲可選趟,代碼生成爲後端趟
源程序 -> 詞法分析器-> 詞法單元-> 語法分析器 -> 語法分析樹-> 中間代碼生成器-> 三地址代碼
詞法分析器、語法分析器、中間代碼生成器都依賴於符號表
詞法分析器使得翻譯器能夠處理由多個字符組成的構造,好比標識符。標識符由多個字符組成,在語法分析階段看成單元稱爲詞法單元。
語法分析器會生成抽象語法樹,它表示了源程序的層次化語法結構
中間代碼生成器會將語法樹進一步翻譯爲三地址代碼嗎如,x = y op z
詞法分析是編譯的第一階段。主要任務是讀入源程序的輸入字符、將它們組成詞素,生成並輸出一個詞法單元序列,每一個詞法單元對應於一個詞素。詞法單元序列被輸出到語法分析器進行語法分析。詞法分析器還要和符號表進行交互。當詞法分析器發現了一個標識符詞素時,它要將這個詞素添加到符號表中
除了識別詞素以外的其餘任務之一是過濾掉源程序中的註釋和空白(空格、換行符、製表符以及在輸入中用於分隔詞法單元的其餘字符)另外一個任務是將編譯器生成的錯誤消息與源程序的位置聯繫起來。
一個詞法單元名和一個可選的屬性值組成。理解爲對一個單詞也就是詞素的描述,也能夠稱之爲總稱。好比全部數字的詞法單元都叫number,<=,!=等叫comparison,else就是else
描述了一個詞法單元的詞素可能具備的形式
源程序中國的一個字符序列
經過必定的正則定義匹配相應的模式識別標識符關鍵字保留字,消除空白符
狀態轉換圖有一組被稱爲」狀態「的結點。詞法分析器掃描輸入串的過程當中尋找和某個模式的匹配的詞素,而轉換圖中的每一個狀態表明一個可能在這個過程當中出現的狀況。
初始化時就將各個保留字填入符號表中
爲每一個關鍵字創建單獨的狀態轉換圖
不肯定的有窮自動機:對其邊上的標號沒有任何限制,一個符號標記離開同一狀態的多條邊
肯定的有窮自動機:有且只有一條離開該狀態、以該符號爲標號的邊
容許詞法單元之間出現任意數量的空白,還有程序中會出現註釋當詞法分析器消除了,語法分析器就不須要再考慮了
在讀取字符流的時候須要判斷下一個字符是否是與當前字符須要組合,好比讀了>還須要讀下一個是不是=構成>=
表達式中出現的整形常量能夠建立一個表明整形常量的終結符號入<NUM,31>元組表明有整型常量31
程序設計語言中都有它相應的固定字符串組成的關鍵字,字符串還能夠標識符來做爲變量、數組、函數等命名會將其當作終結符處理
語法分析器從詞法分析器得到一個由詞法單元組成的串,並驗證這個串能夠由源語言的文法生成
一個有窮的非終結符(或變元)的集合;一個有窮的終結符的集合;一個有窮的產生式集合;一個起始非終結符(變元)。
不去判斷做用域等問題造成一個AST
遞歸降低算法其實很簡單,它的基本思路就是按照語法規則去匹配 Token 串。
對於一個非終結符,要從左到右依次匹配其產生式中的每一個項,包括非終結符和終結符。在匹配產生式右邊的非終結符時,要降低一層,繼續匹配該非終結符的產生式。若是一個語法規則有多個可選的產生式,那麼只要有一個產生式匹配成功就行。若是一個產生式匹配不成功,那就回退回來,嘗試另外一個產生式。
包括標識符、關鍵字或運算符拼寫錯誤和沒有在字符串文本上正確地加上引號
包括分號放錯位置、花括號多餘或缺失。
包括運算符合運算份量之間的類型不匹配。例如void某個方法中返回某個值的return語句。
能夠是因程序員的錯誤推理而引發的任何錯誤
語法分析器一旦發現錯誤不斷丟棄輸入中的符號,一次丟棄一個符號,直到找到同步詞法單元集合中的某個元素位置好比分號或者}
當發現一個錯誤時,語法分析器能夠在餘下的輸入上進行局部性糾正。簡單來講就是對一個輸入串進行增刪改。
經過預測可能遇到的常見錯誤,咱們能夠在當前語言的文法中加入特殊的產生式。
在理想狀態下,咱們但願編譯器在處理一個錯誤輸入串時經過最少的改動將其轉化爲語法正確的串。
在語法分析的同時進行語義翻譯叫作語法制導翻譯。經過構造出的語法分析樹,而後經過訪問這棵樹的各個結點來計算結點的屬性值。
是一個上下文無關法和屬性及規則的結合。屬性和文法符號相關聯,規則和產生式相關聯。
其實就是屬性計算。須要判斷AST中節點中所需的類型。經過子節點計算出來的叫S屬性也就是綜合屬性。經過父親節點或者兄弟節點計算出來的叫作I屬性繼承到的屬性
依賴圖描述了某個語法分析數中的屬性實例之間的信息流。從一個屬性實例到另外一個實例的邊表示計算第二個屬性實例時須要第一個屬性實例的值。
也就是一個表達式的值是否須要另一個表達式求出來的值,定義了屬性的求值順序
像 return、break 和 continue 等語句,都與程序的控制流有關,它們必須符合控制流方面的規則。在 Java 這樣的語言中,語義規則會規定:若是返回值不是 void,那麼在退出函數體以前,必定要執行一個 return 語句,那麼就要檢查全部的控制流分支,是否都以 return 語句結尾。
不少語言都支持閉包。而要正確地使用閉包,就必須在編譯期知道哪些變量是自由變量。這裏的自由變量是指在本函數外面定義的變量,但被這個函數中的代碼所使用。
在高級語言裏,咱們會作變量、函數(或方法)和類型的聲明,而後在其餘地方使用它們。這個時候,咱們要找到定義和使用之間的正確引用關係。
在計算機語言裏,類型是數據的一個屬性,它的做用是來告訴編譯器或解釋器,程序能夠如何使用這些數據。
也就是上述的屬性計算(綜合屬性和繼承屬性)
許多方法均可以用於中間表示。包括抽象語法樹和三地址碼。在給定的源語言的一個程序翻譯成特定的目標機器代碼的過程當中,一個編譯器構造出一系列中間表示。
表達式的有向無環圖,是中間代碼在內存中其中一種數據結構。將本來的AST樹形結構中消除了冗餘的子樹,就是其中有可能一個變量被屢次使用,消除多餘的這個變量子樹合成一個。
在三地址代碼中,一條指令的右側最多有一個運算符。三地址碼是一棵語法樹或一個DAG的線性表示形式。
三地址碼基於兩個基本概念:地址和指令。
地址能夠具備以下形式:
名字。源程序名字做爲三地址代碼中的地址,實現中源程序名字會被替換成指定符號表條目的指針。
常量。
編譯器生成的臨時變量。
全部的賦值都是針對具備不一樣名字的變量的
SSA中有一種函數表示將一個變量不一樣的取值合併起來,根據上文的條件來獲得其中的取值
保證運算份量的類型和運算符的預期類型相匹配
根據一個名字的類型,編譯器能夠肯定這個名字在運行時刻須要多大的存儲空間
編譯器建立並管理一個運行時刻環境,它編譯獲得的目標程序就運行在這個環境中。爲源程序中命名的對象分配和安排存儲位置,肯定目標程序訪問變量時使用的機制,過程間的鏈接,參數傳遞機制,以及與操做系統、輸入輸出設備及其餘程序的接口。
從編譯器編寫者的角度來看,正在執行的目標程序在它本身的邏輯空間內運行,其中每一個程序值都在這個空間中有一個地址。對這個邏輯地址空間的管理和組織是由編譯器、操做系統和目標機共同完成的。
靜態屬於編譯時刻,動態屬於運行時刻
棧式分配。一個方法的局部名字在棧中分配空間。
堆存儲。有些數據的生命週期要比創造它的某次方法調用更長,這些數據一般被分配在一個可複用存儲的」堆「中。
有些語言使用過程、函數或方法做爲用戶自定義動做的單元,幾乎全部針對這些語言的編譯器都把它們的運行時刻存儲按照一個棧進行管理。方法被調用時用於存放該方法的局部變量空間被壓入棧。當方法調用結束,這個空間被彈出棧。
它被用來存儲那些生命週期不肯定,或者將生存到被程序顯示刪除爲止的數據。
當程序爲一個變量或對象請求內存時,存儲管管理器產生一段連續的具備被請求大小的堆空間。
存儲管理器把被回收的空間返還到空閒空間的緩衝池中,這樣它能夠複用該空間來知足其餘的分配請求。
編譯器的最後一個步驟就是代碼生成器。以編譯器前端生成的中間表示和相關符號表信息做爲輸入,輸出語義等價的目標程序。
目標程序必須保持源程序的語義含義,還必須具備很高的質量。它必須有效地利用目標機器上的可用資源。
代碼生成器三個主要任務:指令選擇、寄存器分配和指派、以及指令排序。
代碼生成器必須把IR程序映射成爲能夠在目標機上運行的代碼序列。完成這個映射的複雜性由以下的因素決定:
IR的層次、指令集體系結構自己的特性、想要達到的生成代碼的質量。
代碼生成的關鍵問題之一是決定哪一個值放在哪一個寄存器裏面。寄存器是目標機上運行速度最快的單元,可是咱們一般沒有足夠的寄存器來存放全部的值。
計算執行的順序會影響目標代碼的效率。
由於有指令級別的並行因此有的時候指令重排有助於代碼流水線執行的時候,其中有多條代碼能夠同時運行
提供一個固定大小的窗口,並檢查窗口內的指令,看看是否能夠優化。