如果我們是在Linux下開(kāi)發(fā),那Makefile肯定要知道,不懂Makefile,面對(duì)較大的工程項(xiàng)目的時(shí)候就會(huì)比較麻煩,懂得利用開(kāi)發(fā)工具將會(huì)大大提高我們的開(kāi)發(fā)效率,也可以說(shuō)Makefile是必須掌握的一項(xiàng)技能。
一、了解什么是 Makefile
一個(gè)大型工程中的源文件不計(jì)其數(shù),各個(gè)功能或者模塊分別放在不同的目錄下,手動(dòng)敲命令去編譯就帶來(lái)很大的麻煩,那么Makefile可以定義一系列的編譯規(guī)則,哪些文件需要先編譯,哪些文件需要后編譯,哪些文件需要重新編譯,甚至進(jìn)行更復(fù)雜的功能操作,Makefile帶來(lái)的好處就是——“自動(dòng)化編譯”,一旦寫(xiě)好,只需要一個(gè)make命令,整個(gè)工程完全自動(dòng)編譯,極大的提高軟件開(kāi)發(fā)的效率。
make?是一個(gè)命令工具,是一個(gè)解釋Makefile中指令的命令工具,一般來(lái)說(shuō),大多數(shù)的IDE都有這個(gè)命令,比如:Linux下GNU的make、Visual C++的nmake、Delphi的make。可見(jiàn),Makefile都成為了一種在工程方面的編譯方法。當(dāng)然,不同產(chǎn)商的make各不相同,也有不同的語(yǔ)法,但其本質(zhì)都是在 “文件依賴性” 上做文章。
二、明白編譯鏈接過(guò)程
在編寫(xiě)Makefile之前,還是要先了解清楚程序編譯鏈接過(guò)程,無(wú)論是c、c++,首先要把源文件編譯成中間代碼文件,在Windows下也就是 .obj 文件,Unix/Linux下是 .o 文件,即 Object File,這個(gè)動(dòng)作叫做編譯(compile)。然后再把大量的Object File合成執(zhí)行文件,這個(gè)動(dòng)作叫作鏈接(link)。
編譯時(shí),編譯器需要的是語(yǔ)法的正確,函數(shù)與變量的聲明的正確。對(duì)于后者,通常是你需要告訴編譯器頭文件的所在位置(頭文件中應(yīng)該只是聲明,而定義應(yīng)該放在C/C++文件中),只要所有的語(yǔ)法正確,編譯器就可以編譯出中間目標(biāo)文件。一般來(lái)說(shuō),每個(gè)源文件都應(yīng)該對(duì)應(yīng)于一個(gè)中間目標(biāo)文件(O文件或是OBJ文件)。
鏈接時(shí),主要是鏈接函數(shù)和全局變量,所以,我們可以使用這些中間目標(biāo)文件(O文件或是OBJ文件)來(lái)鏈接我們的應(yīng)用程序。鏈接器并不管函數(shù)所在的源文件,只管函數(shù)的中間目標(biāo)文件(Object File),在大多數(shù)時(shí)候,由于源文件太多,編譯生成的中間目標(biāo)文件太多,而在鏈接時(shí)需要明顯地指出中間目標(biāo)文件名,這對(duì)于編譯很不方便,所以,我們要給中間目標(biāo)文件打個(gè)包,在Windows下這種包叫“庫(kù)文件”(Library File),也就是 .lib 文件,在Unix/Linux下是Archive File,也就是 .a 文件,也叫靜態(tài)庫(kù)文件。
總結(jié)一下,編譯鏈接的過(guò)程如下:
源文件首先會(huì)生成中間目標(biāo)文件,再由中間目標(biāo)文件生成執(zhí)行文件。
在編譯時(shí),編譯器只檢測(cè)程序語(yǔ)法,和函數(shù)、變量是否被聲明。如果函數(shù)未被聲明,編譯器會(huì)給出一個(gè)警告,但可以生成Object File。
在鏈接程序時(shí),鏈接器會(huì)在所有的Object File中找尋函數(shù)的實(shí)現(xiàn),如果找不到,那就會(huì)報(bào)鏈接錯(cuò)誤碼(Linker Error),在VC下,這種錯(cuò)誤一般是:Link 2001錯(cuò)誤,意思是說(shuō),鏈接器未能找到函數(shù)的實(shí)現(xiàn)。你需要指定函數(shù)的Object File。
三、編寫(xiě)一個(gè)簡(jiǎn)單的 Makefile
1. Makefile 的基本語(yǔ)法規(guī)則:
?
目標(biāo)?...?:?依賴?... ????????實(shí)現(xiàn)目標(biāo)的具體表達(dá)式(命令) ????????... ????????...
?
目標(biāo)(target):就是一個(gè)目標(biāo)文件,可以是Object 文件,也可以是執(zhí)行文件,還可以是一個(gè)標(biāo)簽(Label);
依賴(prerequisites):就是要生成那個(gè)target所需要的文件或是目標(biāo);
命令(command):Shell命令,也就是make工具需要執(zhí)行的命令。
【總結(jié)】:通過(guò)依賴(prerequisites)中的一些文件生成目標(biāo)(target)文件,目標(biāo)文件要按照命令(command)中定義的規(guī)則來(lái)生成。
2. 來(lái)看一個(gè)簡(jiǎn)單的示例代碼
簡(jiǎn)單寫(xiě)三個(gè)方法文件(openFile.c、readFile.c、writeFile.c)、一個(gè)頭文件(operateFile.h)和一個(gè)主函數(shù)文件(main.c),代碼如下:
?
//?openFile.c #include?"operateFile.h" void?openFile() { ????printf("open?file........... "); }
//?readFile.c #include?"operateFile.h" void?readFile() { ????printf("read?file........... "); }
//?writeFile.c #include?"operateFile.h" void?writeFile() { ????printf("write?file........... "); }
//?operateFile.h #ifndef?__OPERATEFILE_H__ #define?__OPERATEFILE_H__ #include?void?openFile(void); void?readFile(void); void?writeFile(void); #endif
//?main.c #include?#include?"operateFile.h" int?main() { ????openFile(); ????readFile(); ????writeFile(); ???? ????return?0; }
?
3. 根據(jù)上面的語(yǔ)法規(guī)則及編譯鏈接過(guò)程編寫(xiě)一個(gè)Makefile文件
?
main:main.o?openFile.o?readFile.o?writeFile.o??#?main生成所需要的.o文件 ????gcc?-o?main?main.o?openFile.o?readFile.o?writeFile.o??#?生成main的規(guī)則
?
main.o:main.c??#?mian.o文件生成所需要的mian.c文件
????gcc?-c?main.c openFile.o:openFile.c ????gcc?-c?openFile.c readFile.o:readFile.c ????gcc?-c?readFile.c writeFile.o:writeFile.c ????gcc?-c?writeFile.c
clean:????#?需要手動(dòng)調(diào)用
????rm?*.o?main
注意:Makefile的注釋符號(hào)是 ‘#’。
4. 編寫(xiě)完成后,執(zhí)行make命令,make會(huì)在當(dāng)前目錄下找到名字為Makefile或makefile的文件,程序就會(huì)自動(dòng)運(yùn)行,產(chǎn)生相應(yīng)的中間文件和可執(zhí)行文件
a. 如果執(zhí)行make出現(xiàn)如下信息,那就是命令行(makefile中的gcc或者rm)前面沒(méi)有用tab鍵縮進(jìn),不能用空格:
b. 如果執(zhí)行make出現(xiàn)如下信息,那就是你的代碼沒(méi)有修改過(guò),Makefile拒絕你的請(qǐng)求:
這里還會(huì)有一種情況就是如果只修改過(guò)其中一個(gè)文件,那么重新編譯就可以看到只編譯修改的那個(gè)文件,沒(méi)有編譯其他未修改的文件,避免了重復(fù)編譯。這里可以想象在一個(gè)大型源碼的工程或者一個(gè)內(nèi)核源碼,里面的源文件上千或上萬(wàn)個(gè),如果只修改了一個(gè)小問(wèn)題,就要全部重新編譯,就會(huì)花費(fèi)大量編譯的過(guò)程,Makefile就可以避免這個(gè)問(wèn)題,而且支持多線程并發(fā)操作,可以減少很多編譯的時(shí)間,提高工作效率。
那么Makefile是如何判斷文件是否有修改過(guò)呢??
Makefile是通過(guò)對(duì)比時(shí)間戳,當(dāng)我們生成中間文件或可執(zhí)行文件之后,他們的創(chuàng)建時(shí)間肯定要比 .c文件最后修改的時(shí)間晚,如果某個(gè) .c文件有新修改過(guò),它的時(shí)間戳肯定會(huì)比原來(lái)生成中間文件或可執(zhí)行文件的時(shí)間戳晚,這樣就判斷這個(gè) .c文件有被更新過(guò),就會(huì)重新編譯它。
5. 正常運(yùn)行后,執(zhí)行可執(zhí)行文件輸入 ./main 即可,就能看到代碼執(zhí)行的結(jié)果
6. 在makefile文件的最后可以看到有個(gè)clean,這個(gè)clean就是前面所說(shuō)的標(biāo)簽,它不是一個(gè)文件,所以make無(wú)法生成它的依賴關(guān)系和決定它是否要執(zhí)行,只能通過(guò)顯示指定這個(gè)目標(biāo)才可以 ,通過(guò)make clean的指令就可以執(zhí)行clean下面的命令。
到這里,一個(gè)基礎(chǔ)版的Makefile就完成了。
四、Makefile的優(yōu)化
學(xué)會(huì)了編寫(xiě)基礎(chǔ)版的Makefile后,就可以對(duì)剛剛寫(xiě)的Makefile進(jìn)行優(yōu)化。
? 優(yōu)化1:省略命令
我們將上面寫(xiě)的基礎(chǔ)版Makefile改成下面這樣的省略版:
?
main:main.o?openFile.o?readFile.o?writeFile.o??? ????gcc?-o?main?main.o?openFile.o?readFile.o?writeFile.o?? clean:????? ????rm?*.o?main執(zhí)行make后的結(jié)果:
?
可以看到,這些文件都在同一目錄下的時(shí)候,省略版和基礎(chǔ)版的結(jié)果是一樣的,省略版的makefile中去掉了生成main.o、openFile.o、readFile.o和writeFile.o這些目標(biāo)的依賴和生成命令,這就是make的隱含規(guī)則,make會(huì)試圖去自動(dòng)推導(dǎo)產(chǎn)生這些目標(biāo)的依賴和生成命令,這個(gè)行為就是隱含規(guī)則的自動(dòng)推導(dǎo)。
優(yōu)化2:引入變量
這里引入變量的意思有點(diǎn)像使用宏替換,改成$(變量名),$是格式:
?
TARGET?=?main OBJS?=?main.o?openFile.o?readFile.o?writeFile.o CC?=?gcc $(TARGET):$(OBJS) ????$(CC)?-o?$(TARGET)?$(OBJS) clean:????? ????rm $(OBJS)?$(TARGET)
?
優(yōu)化3:引入函數(shù) 格式:$(函數(shù)名? 實(shí)參列表)
#?函數(shù)1 $(wildcard??*.c)? ??#?表示當(dāng)前路徑下的所有的?.c
#?函數(shù)2 $(patsubst?%.c,?%.o,?所有的.c文件)????#?生成中間文件?.o
#?函數(shù)3 $(notdir?xxx)???#?去除xxx文件的絕對(duì)路徑,只保留文件名引入函數(shù)后的Makefile版本可以改寫(xiě)成:
TARGET?=?main? SOURCE?=?$(wildcard?*.c) OBJS?=?$(patsubst?%.c,?%.o,?$(SOURCE)) CC?=?gcc $(TARGET):$(OBJS) ????$(CC)?-o?$(TARGET)?$(OBJS) ???? clean:????? ????rm $(OBJS)?$(TARGET)
?
優(yōu)化4:對(duì)文件進(jìn)行分類管理 在一個(gè)實(shí)際工程項(xiàng)目中程序文件比較多,我們就會(huì)對(duì)文件按照文件類型進(jìn)行分類,分為頭文件、源文件、目標(biāo)文件和可執(zhí)行文件,分別放在不同的目錄中,由Makefile統(tǒng)一管理這些文件,將生產(chǎn)的目標(biāo)文件放在目標(biāo)目錄下,可執(zhí)行文件放到可執(zhí)行目錄下,分類目錄如下圖:
bin目錄:放可執(zhí)行文件
include目錄:放頭文件
obj目錄:放中間目標(biāo)文件
src目錄:放源文件
可見(jiàn)原來(lái)那些文件都不在同一目錄下了,那么這時(shí)候如果還用之前的Makefile,make就沒(méi)法處理了,自動(dòng)推導(dǎo)也會(huì)無(wú)法進(jìn)行,就需要改成如下:
?
INC_DIR?=?./include BIN_DIR?=?./bin SRC_DIR?=?./src OBJ_DIR?=?./obj ? SRC?=?$(wildcard?$(SRC_DIR)/*.c)? ? ? # /*/ OBJ?=?$(patsubst?%.c,?$(OBJ_DIR)/%.o,?$(notdir?$(SRC))) ? TARGET?=?main BIN_TARGET?=?$(BIN_DIR)/$(TARGET) ? CC?=?gcc ? $(BIN_TARGET):$(OBJ) ????$(CC)?$(OBJ)?-o?$@ ? $(OBJ_DIR)/%.o:$(SRC_DIR)/%.c ????$(CC)?-I$(INC_DIR)?-c?$-o?$@ ? clean: ????find?$(OBJ_DIR)?-name?*.o?-exec?rm?-rf?{}?;??#?刪除?.o?文件 ????rm?$(BIN_TARGET)???#?刪除可執(zhí)行文件main在Makefile中,最終要生成可執(zhí)行文件main我們把它叫做終極目標(biāo),其它所有的 .o 文件本身也是一個(gè)目標(biāo),也需要編譯生成,工程里面許多的 .c 就會(huì)生成許多的 .o,每一個(gè) .c 都寫(xiě)一遍目標(biāo)依賴命令顯然是不可行的,于是就有了類似for循環(huán)的東西,把所有目標(biāo)變成一個(gè)集合,但不是真正用for循環(huán),而是使用一些抽象的符號(hào)表示,解釋如下:
?
%.o:所有 .o 結(jié)尾的文件
%.c:所有 .c 結(jié)尾的文件
$@:表示目標(biāo)文件
$<:表示第一個(gè)依賴文件,也叫初級(jí)依賴
$^:表示所有的依賴文件,也叫終極依賴
? 當(dāng)然,不止只有這些符號(hào),只是列舉了上面出現(xiàn)的或者常見(jiàn)的。
執(zhí)行make后的結(jié)果:
? make執(zhí)行后bin目錄里面已經(jīng)生成了可執(zhí)行文件main,obj目錄里面已經(jīng)生成了中間目標(biāo)文件 main.o、openFile.o、readFile.o、writeFile.o,最后執(zhí)行main后的結(jié)果也是和前面基礎(chǔ)版的Makefile的結(jié)果是一樣的。 ?
? ok,看到這里應(yīng)該對(duì)Makefile有了一定的了解,可以動(dòng)手敲一敲用起來(lái)!
?
審核編輯:湯梓紅
評(píng)論