1. 程式人生 > >Linux系統下的編譯、連線與執行

Linux系統下的編譯、連線與執行

眾所周知,我們程式設計師所寫的程式碼都是英文字母與數字的集合,我們人能看懂,但是電腦只能識別高低電壓,也就是所說的01程式碼,它是如何識別我們程式設計師所寫的程式碼呢,比如c語言、c++。

我們所寫的程式碼又是經過了哪些過程之後,計算機就能識別了呢?也就是本文要講的我們所寫的程式碼是如何變成可執行的二進位制檔案的。

這節講的是Linux系統下,我們所寫程式碼檔案(.c/.cpp)檔案是如何變成可執行的二進位制檔案的。

一個cpp檔案要變成可執行檔案要經過以下步驟:

1、預編譯(將 .cpp /c檔案生成 .i 檔案)

在預編譯階段編譯器會來生成 .i 檔案,只要處理規則如下:

(1)、#define   ,巨集替換,也就是將所有用巨集表示的東西都用它原來的屬性替換掉。

(2)、#include ,拷貝標頭檔案  ,將.cpp檔案中新增的所有標頭檔案拷貝進cpp/c檔案中。注意這個過程是遞迴進行的,也就是說被包含的檔案也可能包含其他檔案。

(3)、#if  、#else 、#endif  、  #elif  、#ifdef,處理掉所有的條件預編譯指令。

(4),刪除註釋  ,將所有的註釋刪除 “//”  和 “/**/”

(5)、新增行號和檔案標識,比如#2 “hello.c” 2 ,以便於編譯時編譯器產生除錯呼叫的行號資訊及用於編譯時產生的編譯錯誤或警告時能夠顯示行號。

(6)、保留所有的 #pragam 編譯器指令,因為編譯器需要使用它們。

2、編譯(將 .i 檔案生成 .s 檔案)

在編譯階段也好經過幾個步驟:

(1)、詞法分析。

arr[i] = (i + 4) * 2;

當原始碼程式被輸入到掃描器(Scanner),掃描器的任務就是運用一種類似於 有限狀態機(Finite State Machine)的演算法將原始碼字元序列分割成一系類的記號(Token)。比如上面那行程式碼,總共包含了15個非空字元,經過掃面以後,產生了13個記號,入下表所示。

詞法分析產生的記號一般可以分為以下幾類:關鍵字、識別符號、字面量(包含數字、字串等)和特殊符號(如加號、等號)。在識別記號的同時,掃面器也完成了其他工作。比如將識別符號放到符號表,將數字、字串常量存放到文字表等,以備後面的步驟使用。

(2)、語法分析

語法分析就是由語法分析器(Grammar Parser)將對由掃描器產生的記號進行語法分析,從而產生語法樹(Syntax Tree)。整個分析過程採用了上下文無關語法(Context-free Grammar)的分析手段。由語法分析器生成的語法樹就是表示式(Expression)為結點的樹。

(3)、語義分析

語義分析,由語義分析器(Semantic Analyzer)來完成。編譯器所能分析的語義是靜態語義(Static Semantic),所謂的靜態語義是指在編譯期間可以確定的語義,與之對應的動態語義(Dynamic Semantic)就是隻有在執行期間才能確定的語義。

靜態語義通常包括宣告和型別的匹配,型別的轉換。比如將一個浮點型賦值給一個指標的時候,語義分析程式就會發現這個型別不匹配,編譯器就會報錯。動態語義一般指在執行期間出現的語義相關問題,比如將0作為除數就是一個執行期間的語義錯誤。

經過語義分析階段以後,整個語法樹的表示式都被標識了型別,如果有些型別需要做隱式轉換,語義分析程式就會在語法樹中插入相應的轉換結點。語義分析器還對符號表裡的符號型別也做了更新。

(4)、程式碼優化

3、彙編 (將 .s 檔案生成 .o/.obj 檔案)

在彙編階段要做的事情就是將程式碼指令翻譯成可重入二進位制檔案。彙編器是將彙編程式碼轉變成機器可以執行的指令,每一個彙編語句幾乎都對應一條機器指令。

4、連結(將 .o 檔案連結成 .exe 檔案 )

連結的主要內容就是把各個模組之間相互引用的部分都處理好,是的各個模組之間能夠正確地銜接。連結階段是最後的階段,它也有四個步驟:

(1)、合併段和符號表。連結的.o檔案可以是一個也可以是多個,每一個.o檔案都有自己的段和符號表,連結階段最開始要做的事情就是將所有相同的段和符號表合併。

(2)、符號解釋,也叫符號決議、符號繫結、名稱繫結、名稱決議,甚至還有叫做地址繫結、指令繫結的,但是大體上他們的意思都一樣。

(3)、分配地址和空間

(4)、符號的重定位

在連線的過程中,對其他定義在目標檔案中的函式呼叫的指令需要重新被調整,對使用其他定義在其他目標檔案的變數來說,也存在同樣的的問題。讓我們結合具體的CPU指令來了解這個過程。假設我們有個全域性變數叫var ,它在目標檔案A裡。我們在目標檔案B裡面要訪問這個全域性變數,比如我們在目標檔案B裡有這麼一條指令:

mov    $0x2a, var

這條指令就是給var這個變數賦值0x2a,相當於C語言裡面的語句var = 42;。然後我們編譯目標檔案B,得到這條指令機器碼。

由於在編譯目標檔案B的時候,編譯器並不知道變數var的目標地址,所以編譯器在沒法確定地址的情況下,將這條mov指令的目標地址設定為0,等待聯結器將目標檔案A和B連線起來的時候在將其修正。我們假設A和B連結後,變數var的地址確定下來為0x1000,那麼連結器將會把這個指令的目標地址修改成0x10000。這個修改地址的過程也叫做重定位(Relocation),每個要被修正的地方叫一個重定位入口(Relocation Entry)。重定位所做的事情就是給程式中每個這樣的絕對地址引用的位置“打補丁”,使他們指向正確的地址。