makefile學習 (二)

前一篇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命令是怎樣一個執行的流程。

  1. make命令尋找當前目錄下的makefile,未找到的話就報錯~~(“make: *** No targets specified and no makefile found. Stop.”)

  2. 默認情況下,make命令會尋找makefile中第一個不是以 . 開頭目標,稱之爲"默認目標"(這種默認行爲是可以通過設置特殊變量 .DEFAULT_GOAL 來更改的)。此例中,“默認目標"是"main”。我們之所以把"main"這條規則放在開頭,是因爲我們的預期就是生成可執行文件。

  3. 找到了"main", make會優先依次處理它的依賴文件"main.o"和"hello.o"。這些.o文件是通過編譯源文件得到的,只有對應規則依賴的源文件或頭文件有改動或對應的.o文件不存在時,纔會重新編譯生成。

  4. make遍歷處理完所有的依賴後(其實,make並不知道.c.h是什麼東西,只會把它們當作target去搜尋,結果發現沒有對應的targetmake就傻傻地什麼都不執行,但,此時,make會根據它們自己的規則,自動更新生成的C程序,比如Bison或Yacc生成的程序)。重編譯這些.o文件後,make再決定是否需要鏈接生成"main"。只有"main"不存在,或者任一.o文件比"main"新的時候,纔會鏈接生成"main"。

  5. 最後,沒有被依賴的規則,則不會執行。如果想要單獨執行,就需要使用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,慢慢更新~~

參考

GNU make

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章