我們平時所說的程序,是指雙擊后就可以直接運行的程序,這樣的程序被稱為可執行程序(Executable Program)。在 Windows 下,可執行程序的后綴有.exe和.com(其中.exe比較常見);在類 UNIX 系統(Linux、Mac OS 等)下,可執行程序沒有特定的后綴,系統根據文件的頭部信息來判斷是否是可執行程序。
可執行程序的內部是一系列計算機指令和數據的集合,它們都是二進制形式的,CPU 可以直接識別,毫無障礙;但是對於程序員,它們非常晦澀,難以記憶和使用。
例如,在屏幕上輸出“VIP會員”,C語言的寫法為:
puts("VIP會員");
二進制的寫法為:

你感受一下,直接使用二進制是不是想撞牆,是不是受到一噸重的傷害?
在計算機發展的初期,程序員就是使用這樣的二進制指令來編寫程序的,那個拓荒的年代還沒有編程語言。
直接使用二進制指令編程對程序員來說簡直是噩夢,尤其是當程序比較大的時候,不但編寫麻煩,需要頻繁查詢指令手冊,而且除錯會異常苦惱,要直接面對一堆二進制數據,讓人眼花繚亂。另外,用二進制指令編程步驟繁瑣,要考慮各種邊界情況和底層問題,開發效率十分低下。
這就倒逼程序員開發出了編程語言,提高自己的生產力,例如匯編、C語言、C++、Java、Python、Go語言等,都是在逐步提高開發效率。至此,編程終於不再是只有極客能做的事情了,不了解計算機的讀者經過一定的訓練也可以編寫出有模有樣的程序。
編譯(Compile)
C語言代碼由固定的詞匯按照固定的格式組織起來,簡單直觀,程序員容易識別和理解,但是對於CPU,C語言代碼就是天書,根本不認識,CPU只認識幾百個二進制形式的指令。這就需要一個工具,將C語言代碼轉換成CPU能夠識別的二進制指令,也就是將代碼加工成 .exe 程序的格式;這個工具是一個特殊的軟件,叫做編譯器(Compiler)。
編譯器能夠識別代碼中的詞匯、句子以及各種特定的格式,並將他們轉換成計算機能夠識別的二進制形式,這個過程稱為編譯(Compile)。
編譯也可以理解為“翻譯”,類似於將中文翻譯成英文、將英文翻譯成象形文字,它是一個復雜的過程,大致包括詞法分析、語法分析、語義分析、性能優化、生成可執行文件五個步驟,期間涉及到復雜的算法和硬件架構。對於學計算機或者軟件的大學生,“編譯原理”是一門專業課程,有興趣的讀者請自行閱讀《編譯原理》一書,這里我們不再展開講解。
注意:不了解編譯原理並不影響我們學習C語言,我也不建議初學者去鑽研編譯原理,貪多嚼不爛,不要把自己繞進去。
C語言的編譯器有很多種,不同的平台下有不同的編譯器,例如:
- Windows 下常用的是微軟開發的 Visual C++,它被集成在 Visual Studio 中,一般不單獨使用;
- Linux 下常用的是 GUN 組織開發的 GCC,很多 Linux 發行版都自帶 GCC;
- Mac 下常用的是 LLVM/Clang,它被集成在 Xcode 中(Xcode 以前集成的是 GCC,后來由於 GCC 的不配合才改為 LLVM/Clang,LLVM/Clang 的性能比 GCC 更加強大)。
你的代碼語法正確與否,編譯器說了才算,我們學習C語言,從某種意義上說就是學習如何使用編譯器。
編譯器可以 100% 保證你的代碼從語法上講是正確的,因為哪怕有一點小小的錯誤,編譯也不能通過,編譯器會告訴你哪里錯了,便於你的更改。
鏈接(Link)
C語言代碼經過編譯以后,並沒有生成最終的可執行文件(.exe 文件),而是生成了一種叫做目標文件(Object File)的中間文件(或者說臨時文件)。目標文件也是二進制形式的,它和可執行文件的格式是一樣的。對於 Visual C++,目標文件的后綴是.obj;對於 GCC,目標文件的后綴是.o。
目標文件經過鏈接(Link)以后才能變成可執行文件。既然目標文件和可執行文件的格式是一樣的,為什么還要再鏈接一次呢,直接作為可執行文件不行嗎?
不行的!因為編譯只是將我們自己寫的代碼變成了二進制形式,它還需要和系統組件(比如標准庫、動態鏈接庫等)結合起來,這些組件都是程序運行所必須的。
鏈接(Link)其實就是一個“打包”的過程,它將所有二進制形式的目標文件和系統組件組合成一個可執行文件。完成鏈接的過程也需要一個特殊的軟件,叫做鏈接器(Linker)。
隨着我們學習的深入,我們編寫的代碼越來越多,最終需要將它們分散到多個源文件中,編譯器每次只能編譯一個源文件,生成一個目標文件,這個時候,鏈接器除了將目標文件和系統組件組合起來,還需要將編譯器生成的多個目標文件組合起來。
再次強調,編譯是針對一個源文件的,有多少個源文件就需要編譯多少次,就會生成多少個目標文件。
總結
不管我們編寫的代碼有多么簡單,都必須經過「編譯 --> 鏈接」的過程才能生成可執行文件:
- 編譯就是將我們編寫的源代碼“翻譯”成計算機可以識別的二進制格式,它們以目標文件的形式存在;
- 鏈接就是一個“打包”的過程,它將所有的目標文件以及系統組件組合成一個可執行文件。
如果不是特別強調,一般情況下我們所說的“編譯器”實際上也包括了鏈接器,比如,你使用了哪種編譯器?去哪里下載C語言編譯器?我的編譯器為什么報錯了呢?
