之前對makefile的編寫,限於剛開始接觸,我都比較侷限一些死板的格式,有時候就會顯得有些繁瑣。在進一步瞭解一些系統編譯和連接的知識後,對makefile編寫流程有了一些新的認識,因此來此梳理梳理,方便更爲靈活地編寫makefile。函數
限於makefile認識不足,這裏參考了一篇比較好博文:makefilespa
makefile帶來直接好處就是——「自動化編譯」。一旦寫好,只須要一個make命令,整個工程徹底自動編譯,因此十分方便。而Makefile文件就是告訴make命令怎麼樣地去編譯和連接程序。可是想要比較靈活的運用它,仍是先要熟悉一些關於系統對程序編譯和連接的知識。.net
通常來講,對C、C++程序、先把源文件編譯成中間代碼文件。Linux下是 .o 文件即 Object File,在Windows下也就是 .obj 文件,這個動做叫作編譯(compile)。而後再把大量的.O文件合成執行文件,這個動做叫做連接(link)code
編譯時,編譯器須要的是語法的正確,函數與變量的聲明的正確。對於後者,一般是讓咱們告訴編譯器頭文件的所在位置(頭文件中放聲明,而定義放在C/C++文件中),只要全部的語法正確,編譯器就能夠編譯出中間目標文件。通常來講,每一個源文件都應該對應於一箇中間目標文件(.O文件或是OBJ文件)。blog
連接時,主要是連接函數和全局變量,因此,咱們可使用這些中間目標文件(.O文件或.OBJ文件)來連接咱們的應用程序。連接器並無論函數所在的源文件,只管函數的中間目標文件。在大多數時候,因爲源文件太多,編譯生成的中間目標文件太多,而在連接時須要明顯地指出中間目標文件名,這對於編譯很不方便,因此,咱們要給中間目標文件打個包,在Windows下這種包叫「庫文件」(Library File),也就是 .lib 文件,在Linux下,是Archive File,也就是 .a 文件字符串
總的來講就是,首先源文件-> .o文件,再由.o文件->可執行文件。在編譯時,編譯器只檢測程序語法,和函數、變量是否被聲明。若是函數未被聲明,編譯器會給出一個警告,但能夠生成Object File。而在連接程序時,連接器會在全部的.o文件中找尋函數的實現,若是找不到,那到就會報連接錯誤碼(Linker Error) get
來個例子感覺一下,編譯器
hello: hello.o hello.o: hello.c gcc -c hello.c -o hello.o
這裏make,便會自動編譯了。這當中生成可執行文件hello依賴於hello.o,hello.o 依賴於 hello.c; 最後找到了hello.c即可以gcc生成hello.o這樣日後‘帶’,目標文件的hello便連接上.o文件去執行了。這裏值得注意的是寫gcc命令時須要添上 -c選項,用來保證獲得的.o文件可重連接,否則基本會make報錯(某些狀況如直接gcc hello.c -o hello例外)。
自動化
直白點說,最後生成的可執行文件就是連接.o文件獲得;而.o文件靠着「依賴關係」生成。io
還有注意一點就是在Makefile中的命令(如gcc ..),必需要以[Tab]鍵開始,否則你極可能就會make出錯哦~。
上面例子直接連接一箇中間目標文件,顯得比較簡單,當遇到源文件須要連接多箇中間目標文件時會是怎麼個樣子呢?
好比 分別建立一個加法的add.c 和 add.h ,一個減法 sub.c和 sub.h 最後main.c 來調用add 和 sub實現加減法。此時Makefile 會像這樣
main: main.o add.o sub.o main.o: main.c gcc -c main.c -o main.o add.o: add.c gcc -c add.c -o add.o #加-c 指定生成爲可重連接.o文件 sub.o: sub.c gcc -c sub.c -o sub.o .PHONY:clean clean: -rm -rf *.o
使用看看
從上面注意幾個地方:
①當最終目標文件依賴多個.o時,將依賴的多個.o 一塊兒寫到main: 後面。而後依次以 目標:依賴文件 gcc... 的格式,羅列全部依賴關係
②因爲在上面的過程當中生成了多箇中間.o文件(實際工程中確定是比較多的),因此每次編譯完成,後續基本還須要進行必定的清理工做,這時候就用上一個 "clean" (後面細說一下)來清理。
③ .PHONY意思表示clean是一個「僞目標」。也便是不管clean是否最新,必定執行它。rm命令前面加了一個小減號的意思就是,也許某些文件出現問題,但並不理睬。固然,clean的規則不要放在文件的開頭,不然這就會變成make的默認目標,相信誰也不肯意這樣。不成文的規矩是——「clean歷來都是放在文件的最後」
關於clean:
它只不過是一個動做名字,有點像c語言中的lable同樣,其冒號後什麼也沒有,那麼,make就不會自動去找它的依賴性,也就不會自動執行其後所定義的命令。要執行其後的命令(不只用於clean,其餘lable一樣適用),就要在make命令後明顯得指出這個lable的名字。這樣的方法很是有用,咱們能夠在一個Makefile中定義不用的編譯或是和編譯無關的命令,好比程序的打包,程序的備份,等等。
到這,大體能夠了解了makefile,以及大體怎麼實現makefile.好, 那麼make又是怎麼用makefile進行執行的呢?
一、make會在當前目錄下找名字叫「Makefile」或「makefile」的文件。
二、若是找到,它會找文件中的第一個目標文件(target),在上面的例子中,他會找到「main」這個文件,並把這個文件做爲最終的目標文件。
三、若是main文件不存在,或是main所依賴的後面的 .o 文件的文件修改時間要比main這個文件新,那麼,它就會執行後面所定義的命令來生成main這個文件。
四、若是main所依賴的.o文件也不存在,那麼make會在當前文件中找目標爲.o文件的依賴性,若是找到則再根據那一個規則生成.o文件。(這有點像一個堆棧的過程)
五、固然,你的C文件和H文件是存在的啦,因而make會生成 .o 文件,而後再用 .o 文件生命make的終極任務,也就是執行文件main了。
這就是整個make的依賴性,make會一層又一層地去找文件的依賴關係,直到最終編譯出第一個目標文件。在找尋的過程當中,若是出現錯誤,好比最後被依賴的文件找不到,那麼make就會直接退出,並報錯,而對於所定義的命令的錯誤,或是編譯不成功,make根本不理。make只管文件的依賴性,即若是在我找了依賴關係以後,冒號後面的文件仍是不在,那麼對不起,我就不工做啦。
從前面的makefile編寫來看, 當中咱們每寫一個依賴關係就須要寫一個形如gcc X.c -o X.o生成命令,這裏還好,如果較大的工程,這樣不免就太繁瑣了,因此據瞭解,通常在公司專門編寫makefile的人是不會那樣寫的。還有寫着更簡潔方式,就是利用下面這幾個符號:
$^ 表明全部的依賴文件
$@ 表明全部的目標文件
$< 表明第一個依賴文件
注意$<表明的是依賴關係表中的第一項(若是咱們想引用的是整個關係表,那麼就應該使用$^),具體到咱們這裏就是%.c。而$@表明的是當前語句的目標,即%.o。因而即可以將上面的makefile改寫成
.PHONY:clean main: main.o add.o sub.o main.o: main.c gcc -c $< -o $@ add.o: add.c gcc -c $^ -o $@ sub.o: sub.c gcc -c $^ -o $@ clean: rm -rf *.o
因爲連接依賴的是中間目標文件.o ,若是makefile變得複雜,那麼咱們就有可能會少寫一個依賴關係,獲得.o文件不完整,從而致使編譯失敗。因此,爲了makefile的易維護,在makefile中咱們可使用常量(這裏看到不少人都把它說成變量,我的認爲 它在後面並無被改變,因次叫常量更好)。定義一個常量OBJS來表示全部的.o文件,因而便還可將Makefile寫成這樣:
.PHONY:clean OBJS = main.o\ //\轉義字符 add.o\ sub.o main: $(OBJS) %.o : %.c gcc -c $^ -o $@ clean: -rm -rf $(OBJS)
這樣是否是感受又簡潔了很多! 這裏的%.o : %.c 想必均可以你們均可以猜出來,這表明的意思就是全部的.o文件依賴相應的.C文件,是一個模式規則。這樣一來,make命令就會自動將全部的.c源文件編譯成同名的.o文件。不用這樣便又省去好幾步。
到這,相信聰明的你,能夠更靈活編寫makefile (ヾ(´A`)ノ゚ 打臉,怎能說出如此膨脹的話來)。 無論了,再補充補充關於Makefile的東西 就趕忙溜~~
在應用時候,定下的規則通常這樣:
①若是這個工程沒有編譯過,那麼咱們的全部c文件都要編譯並被連接。
②若是這個工程的某幾個c文件被修改,那麼咱們只編譯被修改的c文件,並連接目標程序。
③若是這個工程的頭文件被改變了,那麼咱們須要編譯引用了這幾個頭文件的c文件,並連接目標程序。
因此只要咱們的makefile寫得夠好,全部的這一切,咱們只用一個make命令就能夠完成,make命令會自動智能地根據當前的文件修改的狀況來肯定哪些文件須要重編譯,從而本身編譯所須要的文件和連接目標程序。