一般寫程式的流程為:編譯-載入-執行。
編譯Compile流程
一個完整的編譯過程通常需要包含以下的4個程序:
1. Preprocess 預處理器。
2. Compiler 編譯器。會處理副檔名.c的檔案,產生副檔名.as的組譯檔。
3. Assembler 組譯器。會處理副檔名.as的組譯檔,產生副檔名.o的中間檔。
4. Linker 連結器。會處理副檔名.o或.obj的中間檔。
預處理器(Preprocess)
預處理器會在進行編譯前先處理原始碼內像#ifdef、#define相對簡單的詞句替換、和一些巨集代換的功能。
編譯器(Compiler)
編譯器是將高階語言所寫的原始程式,翻譯成機器語言組成的目的程式。在編譯過程中,會執行下列的步驟:
* 語彙分析(Lexical analysis)。分析程式中每一個字眼(word):註解(comment, 在編譯過程會被編譯器忽略掉)、關鍵字(keyword, 如int、for、while等)、常數(constant, 如1、12、”embedded”等)、運算子(operator, 如+、-、*、/等)。
* 語法分析(Syntax analysis)。主要是將程式符號,轉換成階層式的語法樹(Syntax tree)符號表示。在這個語法樹中,在正常情況下,階層最高的節點(node)為assign的符號,其餘的節點為其他的運算元符號,而葉子(leaf)就都是變數的標記(token)。
* 語意分析(Semantic Analysis)。是藉由語法樹(Syntax tree)來分析程式的邏輯與語法是否符合規定。這個階段就是用來分析程式的「文法」是否正確,已經從文字符號的階段進入了程式語意的判別。
* 中間碼的產生(Intermediate code generate)。是從語法樹(Syntax tree)中,以一個節點(node)為基本單位,從最底層的節點依序往上,拆解成一個個最基本的運算式,而每一個節點也會賦予一個暫時性的符號。
* 程式碼的最佳化(code optimize)。基本上就是減少一些不必要的暫時性節點符號。當然,另外還有一些特別的最佳化演算法也會在這個階段使用,例如針對迴圈邏輯的最佳化有三種知名的演算法: code motion、induction variable、strength reduction;因為迴圈邏輯在語法上是最沒有執行效率的語法之一,因此需要特別的最佳化。或者,有時候編譯器會調整程式的前後順序,為了在下一階段程式碼的產生過程中,暫存器的使用數目降低。
* 程式碼的產生(code generate)。以c語言為例,這裡就是將最佳化後的中間碼,搭配微處理器的暫存器,逐一轉換成組合語言。
組譯器(Assembler)
組譯器會將組合語言的原始程式,翻譯成機器語言組成的中間檔 ".obj"或".o"。
連結器(Linker)
連結器會將中間檔 obj files 連結起來,找到symbol(函式,變數名)與程式庫(shared obj)中的副程式,產生可執行的obj檔(executable obj)。
小結
「編譯-載入-執行」方式的缺點是產生的 List檔,其定址 Address 為相對位址,也就是說,每個source的各個section (Absolute section除外) 都由位置 "0" 排起,非常不方便作程式 debug用。但是產生 .obj 檔仍然有一個優點,就是會收錄 .lib 檔,以後要重新 make 時,只需要直接指定使用的 library 即可。
因為目前電腦速度已經非常快,對所有檔案做編譯及連結幾乎都是瞬間完成,所產生的 List檔已包含最終的正確位址,用來作為程式的 Debug 非常好用,如果沒有特殊需要的話,已經沒有必要產生中間檔 .obj。
參考資料:
程式Compile流程
Gcc編譯流程解析
嗯嗯~~~基本但重要的觀念~~
回覆刪除