前一篇博文提到編譯的幾個步驟,這一篇來了解下具體每一步都幹了些什麼,好叫心裡有數。詳細的過程,我想只有通過分析乙個具體的編譯器**才好。下面介紹的幾個步驟完成了原始碼檔案經過編譯鏈結後成為可執行檔案:
預編譯後的檔案,不再包含注釋,標頭檔案也插入進來,條件編譯也得到相應的處理。那麼,剩下的就是實實在在的原始碼,需要經過詞法分析,語法分析,語義分析及優化,然後產生彙編**。下面結合具體的**例項講解這些過程:
array[index] = (index + 4) * (2 + 6)
首先,掃瞄器(scanner)會掃瞄預處理後的原始碼檔案,它的作用就是將原始碼中的乙個個字元,劃分為符合詞法規則的不同類別,稱為記號(tokens)。這些tokens是後面步驟處理的最小單位,每一類的記號具有特別的意義。一般tokens的類別有:關鍵字,識別符號,字面量(數字和字串)和特殊符號(如加號,乘號等)。結合後面介紹的語法規則,每一類都有不同處理方法。掃瞄的結果是將這28個非空字元,劃分為16個記號,並且為每個記號歸類:
這些記號必須符合一定規則,例如一般程式語言都有算術運算,那麼符號+,-,*,/等就是合法的記號,同時可能還有運算子過載,既乙個符號同時承載了兩種及以上的意義。c語言規定識別符號必須是數字,字母或者下劃線組成,且首字母不能為數字。
語法分析器在上步的基礎上,通過tokens的型別,判斷是否符合語法規則。語法分析多採用上下文無關語法,最終生成語法樹。上下文無關語法規則描述了程式語言的語法。一般包括乙個終止符的有限集,乙個非終止符的有限集,乙個產生規則的有限集和乙個非終止符的開始集。通常程式語言的語法規則通過巴克斯-諾爾正規化(bnf)表示。凡是能夠通過巴克斯-諾爾正規化推到出的都是符合語法規則的表示式。通過語法分析後,程式的形式變為語法樹,通過樹的資料結構更能表達出程式的本質,也便於進一步處理,而不再是便於人類閱讀的形式了。
語法分析後生成抽象語法樹,能夠發現程式的語法層面的錯誤,但是每個表示式也即語法樹的節點的意義是什麼,該語法樹並沒有交代。這時,通過語義分析,能夠發現表示式及子表示式是否型別匹配,進一步把程式向精確化轉化。這裡提到型別匹配,每一種語言都必須給出程式的基本型別和組合型別,程式的變數,函式或者表示式也必須有型別。每一門語言的型別系統就是定義變數和表示式的型別的子系統。對型別的不同劃分是各門語言的特點所在。
編譯器為了對原始碼程式進行優化,會將上一步生成的語法樹轉化成中間**表示,有三位址碼和p**兩種方式。在一些功能強大的編譯器中,它能對不同語言進行編譯,目標**可以執行在多種平台上,就是利用了中間語言來表示,從而簡化編譯器設計。上面的**用三位址碼表示:
經過優化後可表示:
只用到乙個暫存器器和乙個記憶體就能完成上述表示式的運算。將程式轉化成中間語言表示,後續的處理過程屬於編譯器後端。
**生成器將中間語言表示的程式,用對應的彙編表示出來,這裡有個重要的主題就是暫存器分配,如何高效的利用暫存器來完成**轉化。每個cpu生產廠家的彙編**不同,因此需要針對特點的彙編生成不同的目標**。根據原始碼優化後的中間**生成彙編**:
目標**優化器是在目標**的基礎上進行**優化,比如選擇合適的定址方式,刪除多餘的指令等等。
編譯鏈結過程
在談編譯鏈結過程之前我們需要了解一下虛擬位址空間以及程式在編譯鏈結過程時經過了什麼步驟。虛擬位址空間之前在程序空間的部落格中詳細介紹過了,詳見 上圖就是32位系統中4g虛擬位址空間的分布情況 text 段 指令段,存放的是指令 在程式中,我們把區域性變數定義 區域性變數的 定義是指令而不是資料 還有...
編譯鏈結過程(一)
什麼是編譯?什麼是鏈結?為什麼需要編譯和鏈結?在很久以前,計算機發展的初期,還在用機器語言編寫程式,量比較少時是不需要編譯和鏈結的。因為當時的程式設計師直接編寫機器碼讓計算機執行。每種cpu的指令是不相同的,所以每乙個程式要換一台不同cpu的機器上執行時,需要重新寫程式,而且機器語言 涉及很多計算機...
程式編譯,鏈結過程
c語言的編譯鏈結過程要把我們編寫的乙個c程式 源 轉換成可以在硬體上執行的程式 可執行 需要進行編譯和鏈結。編譯就是把文字形式源 翻譯為機器語言形式的目標檔案的過程。鏈結是把目標檔案 作業系統的啟動 和用到的庫檔案進行組織形成最終生成可載入 可執行 的過程。過程 如下 預處理器 將.c 檔案轉化成 ...