makefile 和程序源文件中的 #include 宏到底什麼關係?

最近開始對makefile感興趣,但是讓我感到困惑的是makefile和#include之間有什麼聯繫嗎?#include本身不是已經可以描述文件直接的依賴關係了嗎?既然如此為什麼還需要makefile?還有,使用makefile還需要在源文件里寫入#include宏嗎?兩者間有什麼關係?


有人抱怨術語多:這個答案有四個術語:dependency, impact, description, solver。

Dependency + Change 產生 Impact。Solver 根據 Description 來決定因果。

==========

首先糾正 @李遙 的一個錯誤:makefile 不是 dependency solver,它是 dependency description。在 make 命令實際運行的時候,make 根據 makefile 的 dependency description,源文件和目標文件的時間戳,這兩者來決定如何重新編譯哪些文件。make 是一個 impact solver,輸入是 dependency description 和文件的更新時間,輸出是最小化的運行命令來實施 impact。

#include 從機制上說,只是一種內容的拷貝,比 makefile 低級,但是它又承載了一種 dependency 信息,所以邏輯上也算一種 dependency description。但是這種表示和 makefile 的表示並不兼容。所以寫 makefile 的時候,需要把 #include 表示的 dependency 重新寫成等價的 makefile rule。這個工作可以被自動化,mkdep 就是自動化這個過程的。

#include 作為一種 dependency description,它的 impact 有兩方面。一是 header 文件被修改時,包含它的文件要被重新編譯。二是包含 header 文件的文件要知道 header 文件的內容。前者的 impact solver 是 make(所以需要手工編寫 makefile 或者用 mkdep)。後者的 impact solver 是 C-preprocessor。這兩個 impact solver 互相併不兼容。需要 #include, makefile, mkdep 來把它們結合起來。所以,makefile 和 #include 是起不同作用的。


1樓很專業了,明白的一聽就明白,不過不明白的可能還是糊塗。本答案都只針對於GNU的工具。

首先大多數人可能不了解make的工作機制到底是怎樣的,所以還是推薦的系統學習下makefile的寫法和make的工作原理。

make的工作原理分為兩個階段,推導階段和執行階段,這跟編譯過程有點類似,但是有很大區別。首先,運行make之後它會對makefile中的變數進行展開,然後根據依賴描述(編譯規則)進行依賴關係推導,推導出需要執行的編譯過程。推導原則詳見gnu的make手冊,但是這裡需要特別注意的是make對於編譯規則的推導原則其實是很簡單的,主要就是看規則描述中彼此的時間新舊的關係(時間戳的大小)。

這裡我們能夠看出來,1樓無比正確,其實make的確不是依賴關係求解器,只是依賴關係描述的執行者,你需要在makefile里明確寫出文件之間的互相依賴,才能讓make正確的按照依賴關係進行編譯。make根本不會管你編譯的是什麼文件,更不會管你編譯的文件內容何如,依賴如何。你不寫明他就不會懂。

到這裡,你會得出一個結論,難道make搞不定類似#inlcude的關係嗎?實際上make的確不是天然就能搞定的,不信你寫一個簡單的文件比如一個helloworld,裡面包含一個.h,類似這樣:

helloworld.h

#define HELLOWORLD "hello world"

helloworld.c

#include &
#include "helloworld.h"

int main(void)
{
printf("%s", HELLOWORLD);
return 0;
}

然後用這個makefile去編譯它:

Makefile

all: helloworld

helloworld: helloworld.c
gcc -o helloworld helloworld.c

第一次執行make,gcc編譯出helloworld,沒任何問題,在運行一次make,我們沒改什麼所以make告訴你不用幹啥。關鍵點來了,現在修改或者touch一下helloworld.h文件之後運行一下make,你會發現make會告訴你還是不用幹啥。

覺得驚訝嗎,其實很正常,因為你makefile中只寫了helloworld依賴於helloworld.c跟helloworld.h沒毛關係makefile當然不會管helloworld,h死活。請記住,make不會求解任何編譯層面的依賴問題,他沒有這個能力。

把Makefile改成這樣才能達到我們的預期

helloworld: helloworld.c helloworld.h
gcc -o helloworld helloworld.c

到這裡你可能有疑問,那項目裡面這麼多頭文件的互相依賴,make都是怎麼搞定的呢?答案這裡不詳細揭曉,推薦大家去仔細學學makefile。有兩點提示,首先這個功能依賴於編譯器,gcc的-M -MM -MD就是專門給make解決這個問題定製的選項,其次,makefile中自己也有include命令。

所以記住兩點,首先make不解決依賴問題,它只執行依賴。第二,編譯器才有能力解決依賴問題。


泛Make系統(含CMake、NMake、Ant等)是依賴關係求解器(dependency solver)

C/C++的宏是文本處理器


簡單說,兩者完全沒有關係。makefile只是構建工具。跟具體語言沒有關係。


分層的思想吧,在makefile里只包含總的路徑,在程序里就可以包含相對路徑了,這樣既簡化了makefile,有對程序有一個提示作用。例如:makefile里加上-I$(KERNEL_DIR)/include,在程序里就可以直接包含其相對路徑下的頭文件,而不是覺得路徑,這樣移植時只修改下makefile就可以了


一樓搞的術語太多了,看了有點頭暈。

個人覺得makefile和程序中的#include沒有直接的關係,當然你可以認為#include隱含一種依賴,但這個依賴是會由makefile自行添加了,你需要人工指定,或者藉助於gcc的M/MM選項來生成depend文件(這個文件實際上也是makefile,在makefile中可以通過makefile的include函數來引入)。

建議看一下gnu make的文檔(info make,當然推薦在Emacs下閱讀,使用info查詢各種gnu程序的文檔),對這些講的很清楚。


推薦閱讀:

如何將人工智慧技術與有機化學合成結合?人工智慧將對有機化學發展產生怎樣的衝擊?
Python 編程,應該養成哪些好的習慣?
為什麼玩C++的都喜歡調用別人的庫?
寫代碼很厲害是怎樣一種體驗?
當碼農難道不需要情商嗎?

TAG:編程 | 計算機 | file | Makefile | make |