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