makefile文件中的依赖关系理解
首先,假設(shè)當(dāng)前工程目錄為prj/,該目錄下有6個文件,分別是:main.c、abc.c、xyz.c、abc.h、xyz.h和Makefile。其中main.c包含頭文件abc.h和xyz.h,abc.c包含頭文件abc.h,xyz.c包含頭文件xyz.h,而abc.h又包含了xyz.h。它們的依賴關(guān)系如圖。
? ? ? ? ? ? ? ? ?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
第一次使用Makefile應(yīng)該寫成這個樣子(假設(shè)生成目標(biāo)main):
1. main:main.o abc.o xyz.o 2. gcc main.o abc.o xyz.o -o main3. main.o:main.c abc.h xyz.h 4. gcc -c main.c –o main.o -g 5. abc.o:abc.c abc.h xyz.h 6. gcc -c abc.c –o abc.o -g 7. xyz.o:xyz.c xyz.h 8. gcc -c xyz.c -o xyz.o -g9. clean: 10. rm main main.o abc.o xyz.o -f雖然這樣Makefile完全符合Makefile的書寫規(guī)則,但是當(dāng)代碼文件再增加幾倍后,再管理這些命令將會是一個噩夢!!!因此Makefile提供了默認規(guī)則和自動推導(dǎo)幫我們完成一些常用功能。然后,我們將Makefile修改如下:
1. EXE=main 2. CC=gcc 3. OBJ=main.o abc.o xyz.o 4. CFLAGS=-g 5. $(EXE):$(OBJ) 6. $(CC) $^ -o $@7. clean: 8. rm $(EXE) $(OBJ) -f變量EXE,CC,OBJ分別代指目標(biāo)程序名,編譯器名,目標(biāo)文件名。CFLAGS是C編譯選項,它會附加在每條編譯命令(gcc -c)之后。$(EXE)是對變量的引用,$^代指所有的依賴項——即$(OBJ),$@代指目標(biāo)項——即$(EXE)。
該命令等價于:$(CC)? $(OBJ) -o $(EXE)。
這個Makefile只有目標(biāo)文件鏈接的命令,源文件的編譯命令都被忽略了!這正是Makefile的自動推導(dǎo)功能——它可以將目標(biāo)文件自動依賴于同名的源文件,即:
1. main.o:main.c 2. gcc -c main.c -o main.o3. abc.o:abc.c 4. gcc -c abc.c -o abc.o5. xyz.o:xyz.c 6. gcc -c xyz.c -o xyz.o按照上述方式,只要工程下增加了源文件后,只需要在OBJ初始化處增加一個*.o即可。但是這種方式是有問題的,Makefile的自動推導(dǎo)功能只會推導(dǎo)出目標(biāo)文件對源文件的依賴關(guān)系,而不會在依賴關(guān)系中添加頭文件!這導(dǎo)致的直接問題就是:當(dāng)?shù)谝淮螆?zhí)行make后,再次修改依賴的abc.h、xyz.h頭文件的內(nèi)容,自動推導(dǎo)功能只會去檢測.c文件的修改時間戳,發(fā)現(xiàn)沒有變化則不會再次編譯生成main.o、abc.o、xyz.o文件,進一步導(dǎo)致不會進行重新make鏈接生成目標(biāo)文件(因為檢測到main.o、abc.o、xyz.o文件沒有變化)!除非修改頭文件后運行一次make clean,再運行make.
為了能讓make自動包含頭文件的依賴關(guān)系,gcc為我們提供了一個編譯選項(gcc -M,對于g++是-MM),能輸出目標(biāo)文件的依賴關(guān)系!比如執(zhí)行:
gcc -M main.c
會終端顯示:
main.o:main.c abc.h xyz.h
注意:如果是不在當(dāng)前路徑下的頭文件會顯示出全路徑!
如果將每個源文件的依賴關(guān)系包含到Makefile里,就可以使得目標(biāo)文件自動依賴于頭文件了!再次修改原先的Makefile:
1. EXE=main 2. CC=gcc 3. SRC=$(wildcard *.c) 4. OBJ=$(SRC:.c=.o) 5. CFLAGS=-g 6. all:depend $(EXE) 7. depend: 8. @(CC)?MM(SRC) > .depend 9. -include .depend 10. $(EXE):$(OBJ) 11. $(CC) $(OBJ) -o $(EXE)12. clean: 13. @rm $(EXE) $(OBJ) .depend -f創(chuàng)建了一個偽目標(biāo):all,它依賴于目標(biāo)depend和實際的目標(biāo)EXE。而depend正是將所有源文件對應(yīng)的目標(biāo)文件的依賴關(guān)系輸入到.depend文件,并被包含在Makefile內(nèi)!這里有幾個細節(jié)需要說明:
1..depend文件是隱藏文件,避免和工程的文件混淆。
2.include命令之前增加符號'-',避免第一次make時由于.depend文件不存在報告錯誤信息。
3.SRC初始化為wildcard *.c表示當(dāng)前目錄下的所有.c源文件,這就省去了我們手動輸入新增的源文件。
4.OBJ初始化為SRC:.c=.o,表示將SRC中所有.c結(jié)尾的文件名替換為.o結(jié)尾的,這樣就自動生成了源文件的目標(biāo)文件序列。
5.clean的rm命令錢@符號表示執(zhí)行該命令時不回顯命令。
這樣,每次執(zhí)行make時都會重新計算目標(biāo)文件的依賴關(guān)系,并輸出到.depend文件,然后包含到Makefile后進行編譯工作,這樣目標(biāo)文件的依賴關(guān)系就不會出錯了!而我們得到了一個能自動包含源文件和識別頭文件依賴關(guān)系的Makefile,將該文件應(yīng)用于任何單目錄的C/C++工程(C++需要修改部分細節(jié),不作贅述)都能正常工作。
但是,這種方式也有一定的不足,當(dāng)頭文件的依賴關(guān)系不發(fā)生變化時,每次make也會重新生成.depend文件。如果這樣使得工程的編譯變得不盡人意,那么我們可以嘗試將依賴文件拆分,使得每個源文件獨立擁有一個依賴文件,這樣每次make時變化的只是一小部分文件的依賴關(guān)系。
1. EXE=main 2. CC=gcc 3. SRC=$(wildcard *.c) 4. OBJ=$(SRC:.c=.o) 5. DEP=(patsubst(SRC)) 6. CFLAGS=-g 7. $(EXE):$(OBJ) 8. $(CC) $^ -o $@ 9. $(DEP):.%.d:%.c 10. @set -e; 11. rm -f $@; 12. $(CC) ?M $< > @.$$$; 13. sed 's,$?\.o[ :]*,\1.o $@ : ,g' < $@. 14. > $@; 15. rm -f @.$$$ 16. -include $(DEP)17. clean: 18. @rm $(EXE) $(OBJ) $(DEP) -f注意,上面10-15行其實是一條命令,make只會創(chuàng)建一個Shell進程執(zhí)行這條命令,這條命令分為5個子命令,用;號隔開。執(zhí)行步驟為:
1)set-e命令設(shè)置當(dāng)前Shell進程為這樣的狀態(tài):如果它執(zhí)行的任何一條命令的退出狀態(tài)非零則立刻終止,不再執(zhí)行后續(xù)命令。@表示makefile執(zhí)行這條命令時不顯示出來
2)把原來的.d文件刪掉。
3)$<依賴的目標(biāo)集(即*.c), -M表示生成文件依賴關(guān)系, $@表示生成的目標(biāo)文件(即*.d),$$表示本身的ProcessID。注意,在Makefile中$有特殊含義,如果要表示它的字面意思則需要寫兩個$,所以Makefile中的四個$傳給Shell變成兩個$,兩個$在Shell中表示當(dāng)前進程的id,一般用它給臨時文件起名,以保證文件名唯一。
4)這個sed命令比較復(fù)雜,就不細講了,主要作用是查找替換,并加入.d的依賴關(guān)系。
5)最后把臨時文件刪掉。
該Makefile增加了一個變量DEP,初始化為patsubst %.c,.%.d,$(SRC),表示將SRC中的以*.c結(jié)尾的源文件名替換為.*.d的形式,比如main.c對應(yīng)著文件.main.d,這就是main.c的依賴關(guān)系文件,且是隱藏的。
為了生成每個源文件的依賴文件,建立了目標(biāo)依賴關(guān)系$(DEP):.%.d:%.c,該關(guān)系表示,對于目標(biāo)DEP,通過$@可以訪問一個依賴文件,通過$>則訪問對應(yīng)的同名源文件。命令部分使用連接,表示當(dāng)前命令作為一個整體在一個進程內(nèi)執(zhí)行。該組命令的含義是:將gcc -M生成的信息輸出到一個臨時文件,然后在:之前加上當(dāng)前的文件名輸出到依賴文件。比如對于main.c生成的臨時文件信息為:
main.o:main.c abc.h xyz.h
處理后依賴文件信息是:
main.o .main.d:main.c abc.h xyz.h
這樣的依賴關(guān)系表示main.o和它的依賴關(guān)系文件的依賴項是一致的,只要相關(guān)的源文件或頭文件發(fā)生了改變,才會重新生成目標(biāo)文件和依賴關(guān)系文件,也就達到了依賴關(guān)系文件單獨更新的目的了。
雖然如此,但是這樣的Makefile也不是完美的。現(xiàn)假設(shè)工程目錄內(nèi)新增一個源文件lmn.c,按照Makefile的指令make后會產(chǎn)生.lmn.d依賴關(guān)系文件。而如果我們再刪除lmn.c源文件后,重新make后.lmn.d依然存在!尤其是當(dāng)重復(fù)增刪很多源文件后,工程目錄下可能會存在很多無用的依賴文件,當(dāng)然這些問題可以通過make clean解決。
通過前邊的討論,我們得到一個能在單目錄工程下工作的通用Makefile,至于是實現(xiàn)為單獨一個依賴文件的形式,還是每個源文件產(chǎn)生一個獨立的依賴文件,要根據(jù)程序作者自己的喜惡來選擇。雖然每種方法都有一些細微的瑕疵,但是不影響這個通用的Makefile的實用性,試想一下在工程目錄下拷貝一份當(dāng)前的Makefile,稍加修改便可以正確的編譯開發(fā),一定會令人心情大好。
參考:https://www.cnblogs.com/sky-heaven/p/9735177.html
參考:https://www.cnblogs.com/lidabo/p/6207175.html
總結(jié)
以上是生活随笔為你收集整理的makefile文件中的依赖关系理解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: hisi mmz内存管理
- 下一篇: 绝对强大的三个LINUX指令: AR,