編譯和鏈結

2021-10-10 09:15:56 字數 2229 閱讀 9091

你是否有過這樣的疑問:我們編寫的源**,經過了現代強大ide的處理,最終生成了可執行檔案,那麼整合開發環境到底執行了哪些具體的動作,經歷了哪些過程生成了可執行檔案呢?帶著這個疑問,我們一起來看一看。

預處理主要的工作為:

替換展開源**中各個使用巨集定義#define的地方

處理源**中所有的條件預編譯指令如#if、#elif、#else等

展開源**中所有的#include預編譯指令,將所包含的標頭檔案插入到預編譯指令的地方(遞迴進行)

刪除所有的注釋符號//及/× ×/

新增行號及檔名標識,以便在編譯出錯時進行提示

保留所有的#pragma編譯器指令,因為這個指令需要被編譯器用到

以linux為例,通過gcc -e a.c -o a.i即可生成預處理後的檔案,通過檢視預處理後的檔案可以定位原始碼中包含的巨集定義引用、標頭檔案插入是否正確。原始檔經過預處理,做完準備工作後就可以進行編譯了。

編譯階段是整個目標檔案生成過程中相對較複雜的階段,編譯階段需要進行將原始碼進行掃瞄、語法分析、語義分析、源**優化、目標**生成、目標**優化這些步驟,最終生成彙編檔案。

首先我們來看一看掃瞄的過程:掃瞄主要是將源**中的識別符號、關鍵字、字面量(包含數字、字串等)、特殊符號(如加號、乘號、等號等)輸入到掃瞄器,掃瞄器根據不同使用者設定的語法規則進行原始碼符號的掃瞄。

完成原始碼掃瞄之後,語法分析器就會分析掃瞄器中的記號,從而形成「語法樹」(由語言分析器產生的以表示式為節點的樹)。比如:a = (2 + 1) * c這個表示式,將會生成如下語法樹:

我們可以看到,整個語法樹是乙個賦值表示式,識別符號變數和數字不能由其他的表示式組成,所以作為葉子節點,左子樹只有乙個葉子節點,右子樹中有加法表示式和乘法表示式。在語法分析階段,大多數的語法規則優先順序及含義就被確定下來了。當語法分析器發現掃瞄其中的一些非法規則時(比如括號不匹配、表示式中缺少操作符等)就會報編譯錯誤。

語法分析完後就是語義分析,語義分析主要是進行宣告和型別的匹配、型別轉換。在語法分析階段,語法分析器只能確定這個表示式有沒有語法錯誤,而不會進行語義判斷,比如兩個位址變數(指標)相乘有沒有意義。語義分析器對語法分析樹中的每個節點進行型別分析轉換及標識,進行宣告和型別匹配的分析,如果型別不匹配,語義分析器會先進行分析,比如乙個浮點型別賦值給整型,語義分析器需要完成這個步驟同時進行提示;乙個浮點型別賦值給乙個指標就會報編譯錯誤;函式宣告和實現不一致會報編譯錯誤。可以在編譯階段確定語義的叫靜態語義,在執行階段確定語義的叫動態語義,比如除0就是動態語義錯誤。

接下來就是源**優化,現代編譯器有許多不同層次的優化,比如某些變數在多次訪問時為了訪問速度的提公升,會暫時放在暫存器中不進行回寫;在編譯階段可以確定某些表示式值的就會直接使用值來簡化運算等等。上面我們提到的表示式a = (1 + 2) * c就會直接被優化為a = 3 * c;

源**優化完成後需要生成目標**和目標**優化。生成目標**就是**生成器將經過上述步驟後的**翻譯成彙編**,這個過程非常依賴目標機器(不同的機器有不同的指令集、字長、暫存器等)。**生成器生成彙編**後,目標**優化器會將目標**進行優化,比如選擇合適的定址方式、刪除多餘指令、使用更簡便的方式替換運算方式等。

使用gcc -s a.i -o a.s可以完成上述步驟。

彙編器的工作其實比較簡單,就是將編譯通過後的目標**通過一定的規則翻譯成機器**。但是有個問題,比如在表示式中引用了其他檔案中的變數或者呼叫了其他檔案中的函式,在彙編階段是無法確認這些變數或函式的位址的,所以在編譯階段會將引用其他地方的變數或函式位址填充乙個隨機值(一般為0),然後在鏈結過程中更新所有引用這些變數或函式的地方。

使用gcc -c a.s -o a.o來完成彙編。

現代程式語言設計中,基本上程式都是分層分模組設計的,所以在本模組中應用其他模組的變數/函式非常常見。那麼鏈結器怎樣做到確定這些符號的位址呢?

最早的鏈結器是通過人工計算相應的位址偏移,然後進行填充,但這個對於程式設計師來說簡直不能忍受,於是人們使用符號標識來表示對應的變數及**段,在變數或函式在多個模組間引用時,可以通過符號來標識位址跳轉,最後由鏈結器來更新這些識別符號的位址。所以鏈結器的主要工作就是位址和空間分配、符號決議(符號標識和位址對應)和重定位(符號標識位址修正更新,重定位)。

使用gcc a.o b.o -o a.out(不指定輸出檔名則預設為a.out)完成目標檔案與庫鏈結生成可執行檔案的過程。

編譯和鏈結

一般來說,無論是c c 首先要把原始檔編譯成中間 檔案,在windows下也就是 obj 檔案,unix下是 o 檔案,即 object file,這個動作叫做編譯 compile 然後再把大量的object file合成執行檔案,這個動作叫作鏈結 link 編譯時,編譯器需要的是語法的正確,函式與...

編譯和鏈結

在多道程式的實現中,要想使原始檔生成可執行檔案通常需要兩個步驟編譯和鏈結,其中編譯是指將原始檔編譯為中間 檔案,在linux中為 o檔案,其實質就是由c或c 等高階語言生成組合語言。生成可執行檔案,我們以編譯c c 為例,在windows中編譯生成的為.obj檔案,在linux unix中生成.o檔...

編譯和鏈結

平時,我們口頭上並不嚴格區分 編譯 compile 與 鏈結 link 這兩個專業術語。例如我們總是說 把那個 c檔案編譯成可執行檔案 寫成命令就是 gcc example.c 這個命令馬上給我們產生乙個 a.out 如果程式沒有錯誤的話 實際上,整個工作至少要分成四個階段,分別由不同的程式完成 第...