C編譯過程總結

2021-07-23 09:28:55 字數 3509 閱讀 9013

編譯程式讀取源程式(字元流),對之進行詞法和語法的分析,將高階語言指令轉換為功能等效的彙編**,再由匯程式設計序轉換為機器語言,並且按照作業系統對可執行檔案格式的要求鏈結生成可執行程式。

編譯的完整過程:c源程式-->預編譯處理(.c)-->編譯、優化程式(.s、.asm)-->匯程式設計序(.obj、.o、.a、.ko)-->鏈結程式(.exe、.elf、.axf等)

讀取c源程式,對其中的偽指令(以#開頭的指令)和特殊符號進行處理。

預編譯程式所完成的基本上是對源程式的「替代」工作。經過此種替代,生成乙個沒有巨集定義、沒有條件編譯指令、沒有特殊符號的輸出檔案。這個檔案的含義同沒有經過預處理的原始檔是相同的,但內容有所不同。下一步,此輸出檔案將作為編譯程式的輸出而被翻譯成為機器指令。

編譯程式所要作得工作就是通過詞法分析和語法分析,在確認所有的指令都符合語法規則之後,將其翻譯成等價的中間**表示或彙編**。

優化處理是編譯系統中一項比較艱深的技術。它涉及到的問題不僅同編譯技術本身有關,而且同機器的硬體環境也有很大的關係。優化一部分是對中間**的優化。這種優化不依賴於具體的計算機。另一種優化則主要針對目標**的生成而進行的。

對於前一種優化,主要的工作是刪除公共表示式、迴圈優化(**外提、強度削弱、變換迴圈控制條件、已知量的合併等)、複寫傳播,以及無用賦值的刪除,等等。後一種型別的優化同機器的硬體結構密切相關,最主要的是考慮是如何充分利用機器的各個硬體暫存器存放的有關變數的值,以減少對於記憶體的訪問次數。另外,如何根據機器硬體執行指令的特點(如流水線、risc、cisc、vliw等)而對指令進行一些調整使目標**比較短,執行的效率比較高,也是乙個重要的研究課題。(有些是不能優化的,要使用volitate關鍵字)

為什麼編譯器都要先生成彙編**?

1、直接把高階語言的源**直接編譯成機器碼的話那要做高階語言到機器碼之間的對映,這樣 每個寫編譯器的都必須 熟練機器碼。(模擬網路的分層,每一層都遮蔽上一層的差異,只需專注本層即可)

2、因為每個機器碼都不同,要做個編譯器,那得做很多個機器的版本,彙編器遮蔽了機器的差異而已

3、彙編是機器指令的助記符,乙個彙編指令就對應一條機器指令(特殊指令除外)除錯起來肯定會比機器指令方       便,這樣優化起來也方便。

彙編過程實際上指把組合語言**翻譯成目標機器指令的過程。對於被翻譯系統處理的每乙個c語言源程式,都將最終經過這一處理而得到相應的目標檔案。目標檔案中所存放的也就是與源程式等效的目標的機器語言**。目標檔案由段組成。通常乙個目標檔案中至少有兩個段:

**段:該段中所包含的主要是程式的指令。該段一般是可讀和可執行的,但一般卻不可寫。

資料段:主要存放程式中要用到的各種全域性變數或靜態的資料。一般資料段都是可讀,可寫,可執行的。

由匯程式設計序生成的目標檔案並不能立即就被執行,其中可能還有許多沒有解決的問題。例如,某個原始檔中的函式可能引用了另乙個原始檔中定義的某個符號(如變數或者函式呼叫等);在程式中可能呼叫了某個庫檔案中的函式,等等。所有的這些問題,都需要經鏈結程式的處理方能得以解決。

鏈結程式的主要工作就是將有關的目標檔案彼此相連線,也即將在乙個檔案中引用的符號同該符號在另外乙個檔案中的定義連線起來,使得所有的這些目標檔案成為乙個能夠誒作業系統裝入執行的統一整體。

為什麼要使用鏈結器?

模組化角度考慮:我們可以把程式分散到不同的小的源**中,而不是乙個巨大的類中。這樣帶來的好處是可以復用常見的功能/庫,比方說 math library,standard c library.

效率角度考慮:改動**時只需要重新編譯改動的檔案,其他不受影響。而常用的函式和功能可以封裝成庫,提供給程式進行呼叫(節省空間)

鏈結器做了什麼?

第一步:符號解析 symbolresolution

我們在**中會宣告變數及函式,之後會呼叫變數及函式,所有的符號宣告都會被儲存在符號表(symbol table)中,而符號表會儲存在由彙編器生成的 object 檔案中(也就是 .o 檔案)。符號表實際上是乙個結構體陣列,每乙個元素包含名稱、大小和符號的位置。在 symbol resolution 階段,鏈結器會給每個符號應用乙個唯一的符號定義,用作尋找對應符號的標誌。

第二步:重定位 relocation

這一步所做的工作是把原先分開的**和資料片段彙總成乙個檔案,會把原先在 .o 檔案中的相對位置轉換成在可執行程式的絕對位置,並且據此更新對應的引用符號(才能找到新的位置)   

(假設在main函式中用了printf函式,因為在編譯main.c的時候編譯器還不知道printf函式的位址,所以在編譯階段只是將乙個「臨時位址」放到目標檔案中,在鏈結階段,這個「臨時位址」將被修正為正確的位址,這個過程叫重定位。)

所謂的物件檔案(object file)實際上是乙個統稱,具體來說有以下三種形式:

1、可重定位目標檔案 relocatableobject file (.o file)

每個 .o 檔案都是由對應的 .c 檔案通過編譯器和彙編器生成,包含**和資料,可以與其他可重定位目標檔案合併建立乙個可執行或共享的目標檔案

2、可執行目標檔案 executableobject file (a.out file)

由鏈結器生成,可以直接通重載入器載入到記憶體中充當程序執行的檔案,包含**和資料

3、共享目標檔案 shared objectfile (.so file)

在 windows 中被稱為 dynamic link libraries(dlls),是類特殊的可重定位目標檔案,可以在鏈結(靜態共享庫)時加入目標檔案或載入時或執行時(動態共享庫)被動態的載入到記憶體並執行

在c語言中,函式和初始化的全域性變數(包括顯示初始化為0)是強符號,未初始化的全域性變數是弱符號。

對於它們,下列三條規則使用: ①

同名的強符號只能有乙個,否則編譯器報"重複定義"錯誤。

② 允許乙個強符號和多個弱符號,但定義會選擇強符號的。

③如果有多個弱符號,隨便選擇乙個

如何避免弱符號的壞處呢?

1、上策:想辦法消除全域性變數。全域性變數會增加程式的耦合性,對他要控制使用。如果能用其他的方法代替最好。

2、中策:實在沒有辦法,那就把全域性變數定義為static,它是沒有強弱之分的。而且不會和其他的全域性符號產生衝突。至於其他檔案可能對他的訪問,可以封裝成函式。把乙個模組的資料封裝起來是乙個好的實踐。

3、下策:把所有的符號全部都變成強符號。所有的全域性變數都初始化,記住,是所有的,哪怕初始值是0都行。如果乙個沒有初始化,就可能會和其他人產生衝突,儘管別人初始化了。(自己寫**測試一下)。

4、必備之策:gcc提供了乙個選項,可以檢查這類錯誤:-fno-common。

想詳細理解強弱符號的可參閱

靜態鏈結

就是在生成可執行檔案的時候,把所有需要的函式的二進位制**都包含到可執行檔案中去。

靜態鏈結看起來很簡單,但是有些不足。其中之一就對磁碟空間和記憶體空間的浪費。標準庫中那些函式會被放到每個靜態鏈結的可執行檔案中,在執行的時候,這些重複的內容也會被不同的可執行檔案載入到記憶體中去。同時,如果靜態庫有更新的話,所有可執行檔案都得重新鏈結才能用上新的靜態庫。

動態鏈結

就是為了解決這個問題而出現的。所謂動態鏈結就是在執行的時候再去鏈結。

C語言編譯過程總結詳解

c 語言的編譯鏈結過程要把我們編寫的乙個c程式 源 轉換成可以在硬體上執行的程式 可執行 需要進行編譯和鏈結。編譯就是把文字形式源 翻譯為機器語言形式的目標檔案的過程。鏈結是把目標檔案 作業系統的啟動 和用到的庫檔案進行組織形成最終生成可執行 的過程。過程 如下 從圖上可以看到,整個 的編譯過程分為...

C語言編譯過程總結詳解

c語言的編譯鏈結過程要把我們編寫的乙個c程式 源 轉換成可以在硬體上執行的程式 可執行 需要進行編譯和鏈結。編譯就是把文字形式源 翻譯為機器語言形式的目標檔案的過程。鏈結是把目標檔案 作業系統的啟動 和用到的庫檔案進行組織形成最終生成可執行 的過程。過程 如下 從圖上可以看到,整個 的編譯過程分為編...

C語言編譯過程總結詳解

c語言的編譯鏈結過程要把我們編寫的乙個c程式 源 轉換成可以在硬體上執行的程式 可執行 需要進行編譯和鏈結。編譯就是把文字形式源 翻譯為機器語言形式的目標檔案的過程。鏈結是把目標檔案 作業系統的啟動 和用到的庫文 件進行組織形成最終生成可執行 的過程。過程 如下 從圖上可以看到,整個 的編譯過程分為...