分離編譯(separate compilation)
想像一個由五十個原始檔組成的程式,而你只在其中一個裡修了個打字錯誤。每次都得把整個東西從頭重建嗎?分離編譯給出的答案是否:每個原始檔各自編譯成自己的目的檔,只有真正改動過的檔案需要重新編譯。先前留下的目的檔被重用,最後一個快速的連結步驟把一切繫在一起。這就像重印書中的一頁,相對於重印整本書的差別。
它的運作方式建立在編譯單元以及宣告/定義的切分之上。每個 .c 檔孤立地被轉成一個 .o 檔,只知道透過標頭檔給它的宣告,而不知道其他檔案的完整程式碼。當檔案 A 呼叫定義在檔案 B 中的函式時,A 是針對 B 的宣告(來自標頭檔的承諾)編譯的,並把真正的位址留作未定義符號;連結器事後填補它。所以編譯器從不需要一次看到所有原始碼,任何檔案都能獨立地、以任意順序、甚至跨多個處理器核心平行地編譯。
這就是為什麼 C 與 C++ 專案圍繞著標頭檔與原始檔來組織,也是為什麼建構工具會追蹤哪些檔案改動過,好重建最少所需的部分。回報是大型程式庫上的速度:編輯一個檔案意味著重新編譯一個檔案,而非數百萬行。代價是它所要求的紀律,亦即必須讓標頭檔與原始檔保持一致,以及當它們不一致時出現的連結錯誤(函式被宣告卻從未被定義,或被定義兩次)。分離編譯正是連結之所以作為一個獨立階段存在的原因。
$ gcc -c main.c # -> main.o (編輯後只有這個檔案重新編譯) $ gcc -c utils.c # -> utils.o (未改動:重用舊的 .o,跳過重新編譯) $ gcc main.o utils.o -o app # 把目的檔做最後一個快速連結
改一個檔案、只重編它、重用其餘的、再重新連結:這就是分離編譯。
分離編譯意味著編譯器從不一次看到整個程式,所以跨檔的錯誤(缺少或重複的定義)要到連結器執行時才會被抓到。