前一篇makefile學習 (一)入門了,這一篇慢慢開始加深難度咯。
項目結構
項目文件相變多了丟丟(3個)~~不再是單個文件,而是文件之間有包含關係,以下的makefile
均基於此項目結構進行說明。目錄結構如下:
.
├── hello.c
├── hello.h
├── main.c
└── makefile
其中,main.c調用了頭文件hello.h中聲明的sayHello()
這個方法,而hello.c是sayHello()
的實現源文件。(項目結構很簡單了,不用present源碼了吧)
暴力的makefile
以你的暴脾氣,是不是一行就搞定了呢?如下。
main: main.c hello.c hello.h
gcc main.c hello.c -o main
固然可以,但是不推薦,這樣就體現不出來makefile
的優勢了。那makefile
都有些什麼優勢呢?
-
自動跟蹤文件的改動情況,生成對應的目標文件。也就是文件不改動的話,不用再重新編譯或鏈接~~要知道在大項目中,這可以節約很多很多的時間。
-
其他 (這個
其他
是不是很精髓?\壞笑)
如果這樣寫的話,只改動main.c,hello.c也會重新被編譯一遍,進而連接生成目標文件"main".
完整的makefile及make命令執行流程
比較好的做法是,將每個實現的源文件單獨編譯,生成.o
文件(在編譯過程中,這個.o
文件也叫目標文件(object file),爲了防止和makefile的目標文件(target file)混淆,以後都將其稱爲.o
文件,其實二者的英文名稱是不一樣的),如下。
main: main.o hello.o
gcc main.o hello.o -o main
main.o:main.c hello.h
gcc -c main.c -o main.o
hello.o:hello.c hello.h
gcc -c hello.c -o hello.o
這樣,當改動了main.c,只需重新編譯main.c了,進而再鏈接新的main.o和老的hell.o生成目標文件"main". 是不是速度會快一丟丟了呢?
嗯,這個有那麼一點點複雜了,我們先搞清楚make
命令是怎樣一個執行的流程。
-
make
命令尋找當前目錄下的makefile
,未找到的話就報錯~~(“make: *** No targets specified and no makefile found. Stop.”) -
默認情況下,
make
命令會尋找makefile
中第一個不是以.
開頭目標,稱之爲"默認目標"(這種默認行爲是可以通過設置特殊變量.DEFAULT_GOAL
來更改的)。此例中,“默認目標"是"main”。我們之所以把"main"這條規則放在開頭,是因爲我們的預期就是生成可執行文件。 -
找到了"main",
make
會優先依次處理它的依賴文件"main.o"和"hello.o"。這些.o
文件是通過編譯源文件得到的,只有對應規則依賴的源文件或頭文件有改動或對應的.o
文件不存在時,纔會重新編譯生成。 -
make
遍歷處理完所有的依賴後(其實,make
並不知道.c
,.h
是什麼東西,只會把它們當作target
去搜尋,結果發現沒有對應的target
,make
就傻傻地什麼都不執行,但,此時,make
會根據它們自己的規則,自動更新生成的C程序,比如Bison或Yacc生成的程序)。重編譯這些.o
文件後,make
再決定是否需要鏈接生成"main"。只有"main"不存在,或者任一.o
文件比"main"新的時候,纔會鏈接生成"main"。 -
最後,沒有被依賴的規則,則不會執行。如果想要單獨執行,就需要使用
make rule_name
的形式指定要執行的規則。如make main.o
具有普通變量的makefile
嗯。每次都要寫重複寫依賴文件名字,那麼長一串,好難寫啊!一不小心就寫錯了!沒錯,又該介紹makefile的新特徵了~
-
makefile中可以設置普通變量,特殊變量~
-
其他
這樣,有了變量,makefile又可以精簡一丟丟了,拿"main"這條規則舉例吧~
objects = main.o hello.o
main: $(objects)
gcc $(objects) -o main
我們設置了一個叫"objects"的變量,是不是看起來簡潔很多了。官方標準推薦,所有.o
文件組成的一個列表命名應該爲"objects", “OBJECTS”, “objs”, “OBJS”, “obj” 或者 "OBJ "。
瘦身的makefile
嘖嘖~,文件還是不夠簡潔,我們繼續利用makefile的特性精簡吧。
-
不必寫出編譯單個源文件的命令(
recipe
),因爲make
有隱式規則採用"cc -c"命令從對應的源文件生成.o
文件,此時,對應的.c
源文件也會隱式的加入到依賴文件列表中。 -
其他
我們的makefile又可以精簡了(省略在依賴列表中聲明.c
和生成.o
的編譯命令)
objects = main.o hello.o
main: $(objects)
gcc $(objects) -o main
main.o: hello.h
hello.o: hello.h
再瘦身的makefile
額~~什麼?還不夠精簡嗎?是的,所有.o
文件都僅依賴同樣的依賴文件"hello.h"。召喚神龍,makefile特性又出來了。
-
當所有
.o
文件通過隱式規則創建的時候,它們可以根據prerequisites
分組,而不是根據target
分組。 -
其他
objects = main.o hello.o
main: $(objects)
gcc $(objects) -o main
$(objects): hello.h
他們都依賴於"hello.h",所以根據"hello.h"分組,這兒比較巧合,剛好所有的.o
文件都僅依賴"hello.h"。
瘦不動了的makefile
你以爲這就完了嗎?No!它說它還能更簡潔。這兒直接給出栗子吧, $@
表示目標文件,$^
或$+
表示所有依賴文件,這樣也能避免把文件名寫錯了,寫多個rule
的時候,只需copy,然後修改target
名和依賴文件名即可。
objects = main.o hello.o
main: $(objects)
gcc $^ -o $@
$(objects): hello.h
其他小知識
-
makefile
的名字可以是"GNUmakefile"(GNU make特有)、“makefile”、“Makefile”,make
命令會按照這個順序搜索。官方推薦取名爲"Makefile",因爲它會顯示在目錄列表的前面(用ls -l
看看效果哦)。當然了,也可以用-f filename
或--file=filename
指定makefile
的文件名。 -
如果
make
沒有搜索到這些文件名,那麼就必須指定一個目標,make
會嘗試使用隱式規則去生成。假如當前文件夾中沒有"makefile",也可以使用make main.o
來生成。 -
makefile
可以使用反斜槓\
換行。
xx \ # \前有一個空格
xx
# 等價於 xx xx
xx$\ # \前有一個$
xx
# 等價於 xxxx, 其推導規則是`xx$ xx`, 而 `$ `,是一個不存在的變量,會被替換成空字符,所以得到的結果是`xxxx`
xx\ # \前沒有空格
xx
# 等價於 xx\xx 此時會有
- 其他
代碼呀,可以參考我的GitHub,慢慢更新~~
參考