您好,登錄后才能下訂單哦!
如果我們將同一個(gè)目標(biāo)的命令拆分的寫到不同地方,會(huì)發(fā)生什么呢?我們來(lái)看看下面的代碼
.PHONY : all all : @echo "command-1" VAR := test all : @echo "all : $(VAR)"
我們來(lái)分析下,這份代碼中有兩個(gè)目標(biāo) all,那么我們?cè)趫?zhí)行 make 的時(shí)候。它到底是執(zhí)行哪個(gè)呢?一個(gè)可能是兩個(gè)都執(zhí)行,另一個(gè)就是執(zhí)行第一個(gè),因?yàn)槟J(rèn)的是執(zhí)行第一個(gè)目標(biāo)。下來(lái)我們來(lái)看看執(zhí)行結(jié)果
我們看到它說(shuō) all 重復(fù)了,便忽略了前面的 all 命令。最終執(zhí)行的是最后一個(gè) all 命令。因此,當(dāng) makefile 中出現(xiàn)同名目標(biāo)時(shí),會(huì)將所有的依賴合并在一起,成為目標(biāo)的最終依賴;當(dāng)多處出現(xiàn)同一目標(biāo)的命令時(shí),make 發(fā)出警告,所有之前定義的命令被最后定義的命令取代。注意:當(dāng)使用 include 關(guān)鍵字包含其他文件時(shí),需要確保被包含文件中的同名目標(biāo)只要依賴,沒(méi)有命令;否則,同名目標(biāo)的命令將被覆蓋!我們還是來(lái)看看,再建立一個(gè)新的 makefile.1
makefile.1 源碼
all : @echo "this is command from makefile.1"
makefile 源碼
.PHONY : all VAR := test all : @echo "all : $(VAR)" include makefile.1
我們來(lái)看看編譯結(jié)果,看看打印出的是不是 all : test
我們看到輸出的是 "this is command from makefile.1,并不是我們所期望的 all : test。其實(shí)也不難理解,因?yàn)槲覀儼?makefile.1 中也包含了 all 命令,因此將上面的 all 給替換了。換句話說(shuō),這個(gè)現(xiàn)象有可能會(huì)給我們帶來(lái)意想不到的結(jié)果。那么這也就屬于 makefile 中的一條隱式規(guī)則了,什么是隱式規(guī)則呢?在 make 中,它提供了一些常用的,例行的規(guī)則實(shí)現(xiàn);當(dāng)相應(yīng)目標(biāo)的規(guī)則未提供時(shí),make 嘗試使用隱式規(guī)則。那么我們來(lái)看看下面這個(gè) makefile 能編譯成功嗎?
.PHONY : all SRCS := $(wildcard *.c) OBJS := $(SRCS:.c=.o) app.out : $(OBJS) $(CC) -o $@ $^ $(RM) $^ @echo "Target ==> $@"
我們看看編譯的結(jié)果,是否會(huì)報(bào)錯(cuò)?
我們看到已經(jīng)正確的編譯了,而且結(jié)果也是對(duì)的。那么我們并沒(méi)有在里面定義相應(yīng)的規(guī)則啊,為什么就能正確編譯呢?我們按照它的格式寫個(gè)規(guī)則,再將 cc 換成 gcc 試試,看看結(jié)果是否會(huì)相同?
.PHONY : all CC := gcc SRCS := $(wildcard *.c) OBJS := $(SRCS:.c=.o) app.out : $(OBJS) $(CC) -o $@ $^ $(RM) $^ @echo "Target ==> $@" %.o : %.c @echo "my rule" $(CC) -c -o $@ $^
編譯結(jié)果如下
結(jié)果和之前是一樣的,只不過(guò)是輸出了我們自定義的語(yǔ)句,將 cc 換成了 gcc。那么 cc 是什么呢?為何能編譯源文件呢?cc 第一個(gè) c 是 C 語(yǔ)言,第二個(gè)是 compiler 編譯器的意思。那么這個(gè) cc 編譯器是哪來(lái)的呢?在原來(lái)的 Unix 系統(tǒng)中是有 cc 編譯器的,因?yàn)樗巧虡I(yè)版的,需要收費(fèi),因此在 Linux 系統(tǒng)中也是要支持 cc 的,不過(guò)此 cc 非彼 cc,我們來(lái)看看 cc 最后的原型是什么
我們看到 cc 最后指向的是 gcc,因此使用 cc 進(jìn)行編譯工作其實(shí)也是和使用 gcc 進(jìn)行編譯是一樣的。那么上面的模式規(guī)則是誰(shuí)來(lái)實(shí)現(xiàn)的呢?這個(gè)便是 makefile 中的隱式規(guī)則了。make 提供了生成目標(biāo)文件的隱式規(guī)則,隱式規(guī)則會(huì)使用預(yù)定義變量完成編譯工作;改變預(yù)定義變量將部分改變隱式規(guī)則的行為,當(dāng)存在自定義規(guī)則時(shí),不再使用隱式規(guī)則。當(dāng) make 發(fā)現(xiàn)目標(biāo)的依賴不存在時(shí),嘗試通過(guò)依賴名逐一查找隱式規(guī)則,并且通過(guò)依賴名推導(dǎo)可能需要的源文件,如下
既然隱式編譯這么強(qiáng)大,我們是不是就不用自己編寫相關(guān)規(guī)則了呢?其實(shí)不是的,根據(jù)前輩們的經(jīng)驗(yàn)總結(jié)。在實(shí)際的項(xiàng)目中,我們還是有必要禁止 makefile 中的隱式規(guī)則的,因?yàn)殡[式規(guī)則有副作用。具體表現(xiàn)在:a> 編譯行為難以控制,大量使用隱式規(guī)則可能產(chǎn)生意想不到的編譯行為;b> 編譯效率低下,make 從隱式規(guī)則和自定義規(guī)則中選擇最終使用的規(guī)則。
那么我們下來(lái)來(lái)看看隱式規(guī)則鏈。當(dāng)依賴的目標(biāo)不存在時(shí),make 會(huì)極力組合各種隱式規(guī)則對(duì)目標(biāo)進(jìn)行創(chuàng)建,進(jìn)而產(chǎn)生意料之外的編譯行為!如:需要名為 N.o 的目標(biāo):N.y --> N.c --> N.o。我們還是以代碼為例來(lái)進(jìn)行分析說(shuō)明
main.c 源碼
#include <stdio.h> extern void greeting(); int main() { greeting(); return 0; }
func.p 源碼(這只是一個(gè)測(cè)試的代碼,用的是 Pascal 語(yǔ)言)
unit Func; interface procedure Greeting(); attribute (name = 'greeting'); implementation procedure Greeting(); begin WriteLn('Hello, Pascal!'); end; end.
makefile 源碼
app.out : main.o func.o $(CC) -lstdc++ -o $@ $^
那么我們此時(shí)想要用 func.c 實(shí)現(xiàn)某個(gè)功能,但是現(xiàn)在沒(méi)有 func.c,所以編譯時(shí)肯定會(huì)出錯(cuò)
我們看到 func.o 竟然會(huì)利用 func.p 來(lái)生成,不過(guò)最終還是出錯(cuò)了。出現(xiàn)這樣的錯(cuò)誤,我們是不是很納悶?zāi)??明明是沒(méi)有對(duì)應(yīng)的源文件,但是卻報(bào)的是沒(méi)有 pc 命令。這個(gè)便是 makefile 中的隱式規(guī)則了,那么 make 究竟提供了多少隱式規(guī)則?應(yīng)如何查看查看隱式規(guī)則呢?查看隱式規(guī)則的方法是:查看所有的是 make -p;查看具體的規(guī)則是 make -p | grep "XXX"。下來(lái)我們來(lái)看看 make 中的隱式規(guī)則
因?yàn)樗械囊?guī)則非常多,我們只截取了其中的一段,下來(lái)看看 %.o 對(duì)應(yīng)的規(guī)則
我們看到它默認(rèn)的是支持好多格式的,其中就包括 .c 和 .p 文件,因此它會(huì)將將我們之前的測(cè)試代碼當(dāng)成源文件進(jìn)行編譯了。那么我們應(yīng)該如何避免它的隱式規(guī)則呢?在局部禁用的話,是直接在 makefile 中自定義規(guī)則或者在 makefile 中定義模式(如:%.o : %.p);全局禁用的話則使用 make -r。下來(lái)我們先來(lái)使用下局部禁用的方式,直接在 makefile 中定義模式,但是不做具體處理。
它直接就報(bào)錯(cuò)了,說(shuō)沒(méi)有相應(yīng)的源文。我們?cè)賮?lái)看看使用全局禁用的方式
我們看到使用全局禁用的方式后,連 main.o 的文件也不能生成了。下來(lái)我們來(lái)說(shuō)說(shuō)后綴規(guī)則,它是舊式的“模式規(guī)則”,可以通過(guò)后綴描述的方式自定義規(guī)則。格式如下
后綴規(guī)則分為雙后綴規(guī)則和單后綴規(guī)則。雙后綴規(guī)則是指定義一對(duì)文件后綴(依賴文件后綴和目標(biāo)文件后綴),如:.cpp.o <==> %.o : %.cpp;單后綴規(guī)則是指定義單個(gè)文件后綴(源文件后綴),如:.c <==> % : %.c。后綴規(guī)則有這么幾個(gè)注意事項(xiàng):1、后綴規(guī)則中不允許有依賴;2、后綴規(guī)則必須有命令,否則無(wú)意義;3、后綴規(guī)則將逐步被模式規(guī)則所取代。
下來(lái)我們還是以代碼為例來(lái)進(jìn)行分析說(shuō)明,我們新建一個(gè) func.c 文件用于說(shuō)明問(wèn)題
func.c 源碼
#include <stdio.h> void greeting() { printf("void greeting : %s\n", "hello makefile!"); }
makefile 源碼
app.out : main.o func.o $(CC) -lstdc++ -o $@ $^ .c.o : @echo "my suffix rule" $(CC) -o $@ -c $^ .c : @echo "my suffix rule" $(CC) -o $@ -c $^
我們來(lái)看看編譯結(jié)果
我們看到已經(jīng)正確實(shí)現(xiàn)了。不過(guò)在現(xiàn)在的工程項(xiàng)目中,我們一般都會(huì)摒棄掉后綴規(guī)則,采用的都是模式規(guī)則。通過(guò)對(duì) makefile 中隱式規(guī)則的學(xué)習(xí),總結(jié)如下:1、當(dāng)多處出現(xiàn)同一目標(biāo)的命令時(shí),只有最后定義的命令才有效;2、make 提供了一系列的隱式規(guī)則可使用,當(dāng) makefile 中未定義相關(guān)規(guī)則時(shí),將嘗試使用隱式規(guī)則;3、隱式規(guī)則中可能使用 make 中的預(yù)定義變量,改變預(yù)定義變量可部分改變預(yù)定義規(guī)則的行為;4、隱式規(guī)則可能造成意想不到的編譯行為,在實(shí)際工程項(xiàng)目中盡量不使用隱式規(guī)則;5、后綴規(guī)則是一種舊式的模式規(guī)則,它正逐步被模式規(guī)則所取代。
歡迎大家一起來(lái)學(xué)習(xí) makefile,可以加我QQ:243343083。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。