c編譯器每天都在被使用,但對c編譯器十分了解的人卻不多,而對c編譯器編譯過程有所認知的朋友更是少之又少。在上篇文章中,小編對c編譯器的工作過程有所講解。本文中,為繼續(xù)增進大家對c編譯器的講解,將對編譯器工作過程的余下步驟予以闡述。如果你對本文即將探討的內容存在一定興趣,不妨繼續(xù)往下閱讀。
本文承接上一篇文章而談,將直接探討編譯工作過程的第四步,如果對前三步有疑惑的朋友,可以參閱前文。
1.第四步 頭文件的預編譯(precompilation)
不同的源碼文件,可能引用同一個頭文件(比如stdio.h)。編譯的時候,頭文件也必須一起編譯。為了節(jié)省時間,編譯器會在編譯源碼之前,先編譯頭文件。這保證了頭文件只需編譯一次,不必每次用到的時候,都重新編譯了。
不過,并不是頭文件的所有內容,都會被預編譯。用來聲明宏的#define命令,就不會被預編譯。
2.第五步 預處理(Preprocessing)
預編譯完成后,編譯器就開始替換掉源碼中bash的頭文件和宏。以本文開頭的那段源碼為例,它包含頭文件stdio.h,替換后的樣子如下。
為了便于閱讀,上面代碼只截取了頭文件中與源碼相關的那部分,即fputs和FILE的聲明,省略了stdio.h的其他部分(因為它們非常長)。另外,上面代碼的頭文件沒有經過預編譯,而實際上,插入源碼的是預編譯后的結果。編譯器在這一步還會移除注釋。
這一步稱為“預處理”(Preprocessing),因為完成之后,就要開始真正的處理了。
3.第六步 編譯(CompilaTIon)
預處理之后,編譯器就開始生成機器碼。對于某些編譯器來說,還存在一個中間步驟,會先把源碼轉為匯編碼(assembly),然后再把匯編碼轉為機器碼。
下面是本文開頭的那段源碼轉成的匯編碼。
這種轉碼后的文件稱為對象文件(object file)。
4.第七步 連接(Linking)
對象文件還不能運行,必須進一步轉成可執(zhí)行文件。如果你仔細看上一步的轉碼結果,會發(fā)現(xiàn)其中引用了stdout函數(shù)和fwrite函數(shù)。也就是說,程序要正常運行,除了上面的代碼以外,還必須有stdout和fwrite這兩個函數(shù)的代碼,它們是由C語言的標準庫提供的。
編譯器的下一步工作,就是把外部函數(shù)的代碼(通常是后綴名為.lib和.a的文件),添加到可執(zhí)行文件中。這就叫做連接(linking)。這種通過拷貝,將外部函數(shù)庫添加到可執(zhí)行文件的方式,叫做靜態(tài)連接(static linking),后文會提到還有動態(tài)連接(dynamic linking)。
make命令的作用,就是從第四步頭文件預編譯開始,一直到做完這一步。
5.第八步 安裝(Installation)
上一步的連接是在內存中進行的,即編譯器在內存中生成了可執(zhí)行文件。下一步,必須將可執(zhí)行文件保存到用戶事先指定的安裝目錄。
表面上,這一步很簡單,就是將可執(zhí)行文件(連帶相關的數(shù)據(jù)文件)拷貝過去就行了。但是實際上,這一步還必須完成創(chuàng)建目錄、保存文件、設置權限等步驟。這整個的保存過程就稱為“安裝”(Installation)。
6.第九步 操作系統(tǒng)連接
可執(zhí)行文件安裝后,必須以某種方式通知操作系統(tǒng),讓其知道可以使用這個程序了。比如,我們安裝了一個文本閱讀程序,往往希望雙擊txt文件,該程序就會自動運行。
這就要求在操作系統(tǒng)中,登記這個程序的元數(shù)據(jù):文件名、文件描述、關聯(lián)后綴名等等。Linux系統(tǒng)中,這些信息通常保存在/usr/share/applications目錄下的.desktop文件中。另外,在Windows操作系統(tǒng)中,還需要在Start啟動菜單中,建立一個快捷方式。
這些事情就叫做“操作系統(tǒng)連接”。make install命令,就用來完成“安裝”和“操作系統(tǒng)連接”這兩步。
7.第十步 生成安裝包
寫到這里,源碼編譯的整個過程就基本完成了。但是只有很少一部分用戶,愿意耐著性子,從頭到尾做一遍這個過程。事實上,如果你只有源碼可以交給用戶,他們會認定你是一個不友好的家伙。大部分用戶要的是一個二進制的可執(zhí)行程序,立刻就能運行。這就要求開發(fā)者,將上一步生成的可執(zhí)行文件,做成可以分發(fā)的安裝包。
所以,編譯器還必須有生成安裝包的功能。通常是將可執(zhí)行文件(連帶相關的數(shù)據(jù)文件),以某種目錄結構,保存成壓縮文件包,交給用戶。
8.第十一步 動態(tài)連接(Dynamic linking)
正常情況下,到這一步,程序已經可以運行了。至于運行期間(runtime)發(fā)生的事情,與編譯器一概無關。但是,開發(fā)者可以在編譯階段選擇可執(zhí)行文件連接外部函數(shù)庫的方式,到底是靜態(tài)連接(編譯時連接),還是動態(tài)連接(運行時連接)。所以,最后還要提一下,什么叫做動態(tài)連接。
前面已經說過,靜態(tài)連接就是把外部函數(shù)庫,拷貝到可執(zhí)行文件中。這樣做的好處是,適用范圍比較廣,不用擔心用戶機器缺少某個庫文件;缺點是安裝包會比較大,而且多個應用程序之間,無法共享庫文件。動態(tài)連接的做法正好相反,外部函數(shù)庫不進入安裝包,只在運行時動態(tài)引用。好處是安裝包會比較小,多個應用程序可以共享庫文件;缺點是用戶必須事先安裝好庫文件,而且版本和安裝位置都必須符合要求,否則就不能正常運行。
以上便是此次小編帶來的“c編譯器”相關內容,通過本文,希望大家對編譯器工作過程的這幾個步驟具備一定的認知。如果你喜歡本文,不妨持續(xù)關注我們網(wǎng)站哦,小編將于后期帶來更多精彩內容。最后,十分感謝大家的閱讀,have a nice day!





