首頁 > 軟體

C語言 程式的編譯系統解析

2022-02-28 19:01:10

今天我來補一下C語言篇的程式的編譯的一篇文章,也算是有一個結尾了。

程式的翻譯環境和執行環境

在ANSI C的任何一種實現中,存在兩個不同的環境 :

第1種是翻譯環境,在這個環境中原始碼被轉換為可執行的機器指令。

第2種是執行環境 ,它用於實際執行程式碼。

一個.c的檔案事如何變成.exe的可執行檔案的呢?下面這張圖片是一個大概的過程:

編譯和連結

翻譯環境

  • 組成一個程式的每個原始檔通過編譯過程分別轉換成目的碼( object code )。
  • 每個目標檔案由連結器( linker )捆綁在一 起,形成一個單一-而完整的可執行程式。
  • 連結器同時也會引入標準C函數庫中任何被該程式所用到的函數,而且它可以搜尋程式設計師個人的程式庫,將其需要的函數也連結到程式中。

編譯的幾個階段

接下來,我來用Linux平臺來給大家演示一下編譯的三個過程:

我們先編寫一個簡單C程式:

然後執行這樣一句指令:

gcc test.c

這句指令是讓gcc這個編譯器來編譯我們的程式碼,執行完這句指令我們會發現會生成一個a.out這樣一個可執行檔案,

我們執行再下面這樣一句指令:

./a.out

這樣我們就可以執行這個可執行檔案了,

為了讓大家更好地感受到編譯的過程,我們來一步一步看:

預處理

我們執行再下面這樣一句指令,讓程式碼預處理完之後就停下來:

gcc -E test.c -o test.i

這句指令的意思就是把預處理完之後的資訊輸出到一個test.i的檔案中。

可以發現的是,這裡多了一個test,i的檔案,我們可以開啟看一看:

可以發現的是,有三個點發生了變化:

  • 標頭檔案被展開
  • 宏被文字替換了
  • 註釋被刪除了

我們對原始碼做一個處理,不包含stdio.h的標頭檔案,我們自己寫一個標頭檔案:

再來看一下,預處理後的檔案是什麼樣子的:

效果通上面一樣。

所以預處理的幾個動作

  • 標頭檔案的包含
  • 預處理指令的完成(eg:#define、#pragma…)
  • 註釋的刪除

編譯

執行再下面這樣一句指令讓檔案進行編譯形成組合程式碼:

gcc -S test.c

執行完之後就可以生產出一個test.s的檔案,我們可以開啟看一看:

這裡其實就是組合程式碼。

所以編譯的幾個動作

  • 語法分析
  • 詞法分析
  • 語意分析
  • 符號彙總

符號彙總: 符號彙總的都是全域性的符號。例如上面我們的程式碼標頭檔案就彙總了一個Add,.c檔案就彙總的一個Add和main。

組合

接下來我們執行這樣一條指令:

gcc -c test.c

對原始檔進行組合,結果生成了一個test.o的目標檔案:

開啟這個檔案,我們會發現這是一個我們看不懂的二進位制檔案:

所以其實組合是把組合程式碼轉換為二進位制程式碼(機器指令)。

這個過程還做了一件件事——形成符號表

連結

連結做的兩個事情

  • 合併段表
  • 符號表的合併和符號表的重定位

在Linux系統下,test.o二進位制檔案是用一個elf這樣的格式來組織檔案的。

elf會把檔案組織成一個段。test.o和Add.o都有一個段,那麼我們怎樣才能看懂elf格式的檔案呢?

我們有這樣一個工具叫做readelf,他可以看懂這樣一個檔案,所以我們輸入這樣一條指令:

readelf test.o -a

我們就確實可以看到這樣一個段的存在。

然後這下面還有符號表的彙總:

其實a.out這個檔案也是elf格式的,所以其實連結就是把這幾個elf格式的檔案的段表合併,然後test中的Add函數就有了地址。

執行環境

程式執行的過程:

  • 程式必須載入記憶體中。在有作業系統的環境中:一般這個由作業系統完成。在獨立的環境中,程式的載入必須由手工安排,也可能是通過可執行程式碼置入唯讀記憶體來完成。
  • 程式的執行便開始。接著便呼叫main函數。
  • 開始執行程式程式碼。這個時候程式將使用一個執行時堆疊(stack),儲存函數的區域性變數和返回地址。程式同時也可以使用靜態(static)記憶體,儲存於靜態記憶體中的變數在程式的整個執行過程一直保留他們的值。
  • 終止程式。正常終止main函數;也有可能是意外終止。

到此這篇關於C語言 程式的編譯系統解析的文章就介紹到這了,更多相關C語言 程式編譯內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com