到底什麼是編譯???

2021-10-21 18:01:26 字數 3224 閱讀 3407

我們就以最經典的helloworld程式為例開始吧。我們先使用vim等文字編輯器寫好**,接著在終端執行命令 

gcc helloworld.c -o helloworld 

輸出了可執行檔案helloworld,最後我們在終端執行 ./helloworld,順利地顯示了輸出結果。

可是,簡單的命令背後經過了什麼樣的處理過程呢?gcc真的就「直接」生成了最後的可執行檔案了嗎?

當然不是,我們在gcc編譯命令列加上引數 –verbose要求gcc輸出完整的處理過程(命令列加上 -v 也行),我們看到了一段較長的過程輸出。

輸出結果我們就不完整截圖了,大家有興趣可以自己試驗然後試著分析整個流程。

從圖中我們大致可以看出gcc處理helloworld.c的大致過程:

預處理(prepressing)—>編譯(compilation)—>彙編(assembly)—>鏈結(linking)

預處理我們一步一步來看,首先是預處理,我們看看預處理階段對**進行了哪些處理。

我們在終端輸入指令 gcc -e helloworld.c -o helloworld.i,然後我們開啟輸出檔案。

首先是大段大段的變數和函式的宣告,汗..我們的****去了?我們在vim的普通模式中按下shift+g(大寫g)來到最後,終於在幾千行以後看到了我們可憐兮兮的幾行**。

前面幾千行是什麼呢?

其實它就是 /usr/include/stdio.h 檔案的所有內容,預處理器把所有的#include替換為實際檔案的內容了。這個過程是遞迴進行的,所以stdio.h裡面的#include也被實際內容所替換了。

而且我在helloworld.c裡面的所有注釋被預處理器全部刪除了。就連printf語句前的tab縮排也被替換為乙個空格了,顯得**都不美觀了。

時間關係,我們就不一一試驗處理的內容了,我直接給出預處理器處理的大致範圍吧。

展開所有的巨集定義並刪除 #define

處理所有的條件編譯指令,例如 #if #else #endif #ifndef …

把所有的 #include 替換為標頭檔案實際內容,遞迴進行

把所有的注釋 // 和 / / 替換為空格

新增行號和檔名標識以供編譯器使用

保留所有的 #pragma 指令,因為編譯器要使用

基本上就是這些了。在這裡我順便插播乙個小技巧,在**中有時候巨集定義比較複雜的時候我們很難判斷其處理後的結構是否正確。這個時候我們呢就可以使用gcc的-e引數輸出處理結果來判斷了。

前文中我們提到了標頭檔案中放置的是變數定義和函式宣告等等內容,這些到底是什麼東西呢?其實在比較早的時候呼叫函式並不需要宣告,後來因為「筆誤」之類的錯誤實在太多,造成了鏈結期間的錯誤過多,所有編譯器開始要求對所有使用的變數或者函式給出宣告,以支援編譯器進行引數檢查和型別匹配。

標頭檔案包含的基本上就是這些東西和一些預先的巨集定義來方便程式設計師程式設計。

其實對於我們的helloworld.c程式來說不需要這個龐大的標頭檔案,只需要在main函式前宣告printf函式,不需要#include即可通過編譯。

宣告如下:

1int printf(const char *format, ...);
這個大家就自行測試吧。另外再補充一點,gcc其實並不要求函式一定要在被呼叫之前定義或者宣告(msvc不允許),因為gcc在處理到某個未知型別的函式時,會為其建立乙個隱式宣告,並假設該函式返回值型別為int。

但gcc此時無法檢查傳遞給該函式的實參型別和個數是否正確,不利於編譯器為我們排除錯誤(而且如果該函式的返回值不是int的話也會出錯),所以還是建議大家在函式呼叫前,先對其定義或宣告。

編譯預處理部分說完了,我們接著看編譯。

那麼什麼是編譯?一句話描述:編譯就是把預處理之後的檔案進行一系列詞法分析、語法分析、語義分析以及優化後生成的相應彙編**檔案

這一部分我們不能展開說了,一來我沒有系統學習過編譯原理的內容不敢信口開河,二來這部分要是展開去說需要很厚很厚的一本書了,細節大家就自己學習《編譯原理》吧,相關的資料自然就是經典的龍書、虎書和鯨書了。

gcc怎麼檢視編譯後的彙編**呢?

命令是 gcc -s helloworld.c -o helloworld.s,這樣輸出了彙編**檔案helloworld.s,其實輸出的檔名可以隨意,我是習慣使然。

順便說一句,這裡生成的彙編是at&t風格的彙編**,如果大家更熟悉intel風格,可以在命令列加上引數 -masm=intel ,這樣gcc就會生成intel風格的彙編**了(如圖,這個好多人不知道哦)。不過gcc的內聯彙編只支援at&t風格,大家還是找找資料學學at&t風格吧。

彙編再下來是彙編步驟,我們繼續用一句話來描述:彙編就是將編譯後的彙編**翻譯為機器碼,幾乎每一條彙編指令對應一句機器碼

這裡其實也沒有什麼好說的了,命令列 gcc -c helloworld.c 可以讓編譯器只進行到生成目標檔案這一步,這樣我們就能在目錄下看到helloworld.o檔案了。

linux下的可執行檔案以及目標檔案的格式叫作elf(executable linkable format)。

其實windows下的pe(portable executable)也好,elf也罷,都是coff(common file format)格式的一種變種,甚至windows下的目標檔案就是以coff格式去儲存的。

不同的作業系統之間的可執行檔案的格式通常是不一樣的,所以造成了編譯好的helloworld沒有辦法直接複製執行,而需要在相關平台上重新編譯。當然了,不能執行的原因自然不是這一點點,不同的作業系統介面(windows api和linux的system call)以及相關的類庫不同也是原因之一。

編譯到底是在幹什麼

寫c 都知道,寫完程式要編譯才能形成可執行檔案,那麼,編譯到底是在幹一件什麼樣的事呢?編譯的例子 寫乙個簡單的helloworld.cpp程式 include using namespace std intmain 然後執行g 進行編譯並執行 root vm 238 167 centos g o h...

到底什麼是 O R Mapper

一次和乙個群裡面的朋友聊天,有人說最近發現了新的設計資料庫方法,就是把資料庫的列和物件屬性一一對應,這樣設計很方便。我說寒,那有這麼容易的,實際情況複雜去了,怎麼能一一對應。原文 http dot junkies.weblog seichert posts 4677.aspx 讓我們從o r開始。字...

到底什麼是webservice

傳統上,我們把計算機後台程式 daemon 提供的功能,稱為 服務 service 比如,讓乙個防毒軟體在後台執行,它會自動監控系統,那麼這種自動監控就是乙個 服務 通俗地說,服務 就是計算機可以提供的某一種功能。舉例來說,我現在有一批,需要把它們的大小縮小一半。那麼,我們可以把 縮放 看成是一種服...