Linux——makefile編寫
以前對makefile的編寫,限於剛開始接觸,我都比較局限一些死板的格式,有時候就會顯得有些繁瑣。在進一步了解一些系統編譯和鏈接的知識後,對makefile編寫流程有了一些新的認識,所以來此梳理梳理,方便更靈活地編寫makefile。
限於makefile認識不足,這裏參考了一篇比較好博文:makefile
關於makefile
makefile帶來直接好處就是——“自動化編譯”。一旦寫好,只需要一個make命令,整個工程完全自動編譯,所以十分方便。而Makefile文件就是告訴make命令怎麽樣地去編譯和鏈接程序。但是想要比較靈活的運用它,還是先要熟悉一些關於系統對程序編譯和鏈接的知識。
一般來說,對C、C++程序、先把源文件編譯成中間代碼文件。Linux下是 .o 文件即 Object File,在Windows下也就是 .obj 文件,這個動作叫做編譯(compile)。然後再把大量的.O文件合成執行文件,這個動作叫作鏈接(link)
編譯時,編譯器需要的是語法的正確,函數與變量的聲明的正確。對於後者,通常是讓我們告訴編譯器頭文件的所在位置(頭文件中放聲明,而定義放在C/C++文件中),只要所有的語法正確,編譯器就可以編譯出中間目標文件。一般來說,每個源文件都應該對應於一個中間目標文件(.O文件或是OBJ文件)。
鏈接時,主要是鏈接函數和全局變量,所以,我們可以使用這些中間目標文件(.O
總的來說就是,首先源文件-> .o文件,再由.o文件->可執行文件。在編譯時,編譯器只檢測程序語法,和函數、變量是否被聲明。如果函數未被聲明,編譯器會給出一個警告,但可以生成Object File
直白點說,最後生成的可執行文件就是靠著這種“各各依賴關系”逐步得到的。
來個例子感受一下,
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例外)。
還有註意一點就是在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 一起寫到最前面。然後依次以 目標:依賴文件 gcc... 的格式,羅列所有依賴關系
②由於在上面的過程中生成了多個中間.o文件(實際工程中肯定是比較多的),所以每次編譯完成,需要進行一定的清理工作,這時候就用上一個 "clean" (後面細說一下)來清理。
③ .PHONY意思表示clean是一個“偽目標”。也即是無論clean是否最新,一定執行它。rm命令前面加了一個小減號的意思就是,也許某些文件出現問題,但並不理睬。當然,clean的規則不要放在文件的開頭,否則這就會變成make的默認目標,相信誰也不願意這樣。不成文的規矩是——“clean從來都是放在文件的最後”
關於clean:
它只不過是一個動作名字,有點像c語言中的lable一樣,其冒號後什麽也沒有,那麽,make就不會自動去找它的依賴性,也就不會自動執行其後所定義的命令。要執行其後的命令(不僅用於clean,其他lable同樣適用),就要在make命令後明顯得指出這個lable的名字。這樣的方法非常有用,我們可以在一個Makefile中定義不用的編譯或是和編譯無關的命令,比如程序的打包,程序的備份,等等。
到這,大致可以了解了makefile,以及大致怎麽實現makefile.好, 那麽make又是怎麽用makefile進行執行的呢?
make怎麽執行
1、make會在當前目錄下找名字叫“Makefile”或“makefile”的文件。
2、如果找到,它會找文件中的第一個目標文件(target),在上面的例子中,他會找到“main”這個文件,並把這個文件作為最終的目標文件。
3、如果main文件不存在,或是main所依賴的後面的 .o 文件的文件修改時間要比main這個文件新,那麽,它就會執行後面所定義的命令來生成main這個文件。
4、如果main所依賴的.o文件也不存在,那麽make會在當前文件中找目標為.o文件的依賴性,如果找到則再根據那一個規則生成.o文件。(這有點像一個堆棧的過程)
5、當然,你的C文件和H文件是存在的啦,於是make會生成 .o 文件,然後再用 .o 文件生命make的終極任務,也就是執行文件main了。
這就是整個make的依賴性,make會一層又一層地去找文件的依賴關系,直到最終編譯出第一個目標文件。在找尋的過程中,如果出現錯誤,比如最後被依賴的文件找不到,那麽make就會直接退出,並報錯,而對於所定義的命令的錯誤,或是編譯不成功,make根本不理。make只管文件的依賴性,即如果在我找了依賴關系之後,冒號後面的文件還是不在,那麽對不起,我就不工作啦。
靈活編寫makefile
從前面的makefile編寫來看, 當中我們每寫一個依賴關系就需要寫一個形如gcc X.c -o X.o生成命令,這裏還好,若是較大的工程,這樣難免就太繁瑣了,所以據了解,一般在公司專門編寫makefile的人是不會那樣寫的。還有寫著更簡潔方式,就是利用下面這幾個符號:
$^ 代表所有的依賴文件
$@ 代表所有的目標文件
$< 代表第一個依賴文件
於是便可以將上面的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變得復雜,那麽我們就有可能會忘掉一個需要加入的地方,而導致編譯失敗。所以,為了makefile的易維護,在makefile中我們可以使用常量(這裏看了好多人都把它說成變量,個人認為 它在後面並沒有被改變,因次叫常量更好)那麽還可以定義一個常量來表示所有的.o文件,於是便還可將它們寫成這樣
.PHONY:clean OBJS = main.o\ //\轉義字符 add.o sub.o main: $(OBJS) %.o : %.c gcc -c $^ -o $@ clean: -rm -rf $(OBJS)
這裏的%.o : %.c 想必都可以猜出來,這代表的意思就是所有的.o文件依賴相應的.C文件,這樣便又省去好幾步。
到這,相信聰明的你,可以更靈活編寫makefile了。最後,再補充補充關於Makefile的東西
Makefile還有什麽
- 顯式規則。顯式規則說明了,如何生成一個或多個目標文件。這是由Makefile的書寫者明顯指出,要生成的文件,文件的依賴文件,生成的命令。
- 隱晦規則。由於我們的make有自動推導的功能,所以隱晦的規則可以讓我們比較簡略地書寫Makefile,這是由make所支持的。
- 變量的定義。在Makefile中我們要定義一系列的變量,變量一般都是字符串,這個有點像你C語言中的宏,當Makefile被執行時,其中的變量都會被擴展到相應的引用位置上。
- 文件指示。其包括了三個部分,一個是在一個Makefile中引用另一個Makefile,就像C語言中的include一樣;另一個是指根據某些情況指定Makefile中的有效部分,就像C語言中的預編譯#if一樣;還有就是定義一個多行的命令。有關這一部分的內容,我會在後續的部分中講述。
- 註釋。Makefile中只有行註釋,和UNIX的Shell腳本一樣,其註釋是用“#”字符,這個就像C/C++中的“//”一樣。如果你要在你的Makefile中使用“#”字符,可以用反斜杠進行轉義,如:“\#”。
在工程應用時,我們的規則一般這樣:
①如果這個工程沒有編譯過,那麽我們的所有c文件都要編譯並被鏈接。
②如果這個工程的某幾個c文件被修改,那麽我們只編譯被修改的c文件,並鏈接目標程序。
③如果這個工程的頭文件被改變了,那麽我們需要編譯引用了這幾個頭文件的c文件,並鏈接目標程序。
所以只要我們的makefile寫得夠好,所有的這一切,我們只用一個make命令就可以完成,make命令會自動智能地根據當前的文件修改的情況來確定哪些文件需要重編譯,從而自己編譯所需要的文件和鏈接目標程序。
Linux——makefile編寫