到此爲止,讀者已經瞭解瞭如何在Linux下使用編輯器編寫代碼,如何使用Gcc把代碼編譯成可執行文件,還學習瞭如何使用Gdb來調試程序,那麼,所有的工作看似已經完成了,爲什麼還需要Make這個工程管理器呢?
所謂工程管理器,顧名思義,是指管理較多的文件的。讀者可以試想一下,有一個上百個文件的代碼構成的項目,如果其中只有一個或少數幾個文件進行了修改,按照之前所學的Gcc編譯工具,就不得不把這所有的文件重新編譯一遍,因爲編譯器並不知道哪些文件是最近更新的,而只知道需要包含這些文件才能把源代碼編譯成可執行文件,於是,程序員就不能不再重新輸入數目如此龐大的文件名以完成最後的編譯工作。
但是,請讀者仔細回想一下本書在3.1.2節中所闡述的編譯過程,編譯過程是分爲編譯、彙編、鏈接不同階段的,其中編譯階段僅檢查語法錯誤以及函數與變量的聲明是否正確聲明瞭,在鏈接階段則主要完成是函數鏈接和全局變量的鏈接。因此,那些沒有改動的源代碼根本不需要重新編譯,而只要把它們重新鏈接進去就可以了。所以,人們就希望有一個工程管理器能夠自動識別更新了的文件代碼,同時又不需要重複輸入冗長的命令行,這樣,Make工程管理器也就應運而生了。
實際上,Make工程管理器也就是個“自動編譯管理器”,這裏的“自動”是指它能夠根據文件時間戳自動發現更新過的文件而減少編譯的工作量,同時,它通過讀入Makefile文件的內容來執行大量的編譯工作。用戶只需編寫一次簡單的編譯語句就可以了。它大大提高了實際項目的工作效率,而且幾乎所有Linux下的項目編程均會涉及到它,希望讀者能夠認真學習本節內容。
1 Makefile基本結構
Makefile是Make讀入的惟一配置文件,因此本節的內容實際就是講述Makefile的編寫規則。在一個Makefile中通常包含如下內容:
· 需要由make工具創建的目標體(target),通常是目標文件或可執行文件;
· 要創建的目標體所依賴的文件(dependency_file);
· 創建每個目標體時需要運行的命令(command)。
它的格式爲:
target: dependency_files
command
例如,有兩個文件分別爲hello.c和hello.h,創建的目標體爲hello.o,執行的命令爲gcc編譯指令:gcc –c hello.c,那麼,對應的Makefile就可以寫爲:
#The simplest example
hello.o: hello.c hello.h
gcc –c hello.c –o hello.o
接着就可以使用make了。使用make的格式爲:make target,這樣make就會自動讀入Makefile(也可以是首字母小寫makefile)並執行對應target的command語句,並會找到相應的依賴文件。如下所示:
[root@localhost makefile]# make hello.o
gcc –c hello.c –o hello.o
[root@localhost makefile]# ls
hello.c hello.h hello.o Makefile
可以看到,Makefile執行了“hello.o”對應的命令語句,並生成了“hello.o”目標體。
|
注意 |
在Makefile中的每一個command前必須有“Tab”符,否則在運行make命令時會出錯。 |
2 Makefile變量
上面示例的Makefile在實際中是幾乎不存在的,因爲它過於簡單,僅包含兩個文件和一個命令,在這種情況下完全不必要編寫Makefile而只需在Shell中直接輸入即可,在實際中使用的Makefile往往是包含很多的文件和命令的,這也是Makefile產生的原因。下面就可給出稍微複雜一些的Makefile進行講解:
sunq:kang.o yul.o
Gcc kang.o bar.o -o myprog
kang.o : kang.c kang.h head.h
Gcc –Wall –O -g –c kang.c -o kang.o
yul.o : bar.c head.h
Gcc - Wall –O -g –c yul.c -o yul.o
在這個Makefile中有三個目標體(target),分別爲sunq、kang.o和yul.o,其中第一個目標體的依賴文件就是後兩個目標體。如果用戶使用命令“make sunq”,則make管理器就是找到sunq目標體開始執行。
這時,make會自動檢查相關文件的時間戳。首先,在檢查“kang.o”、“yul.o”和“sunq”三個文件的時間戳之前,它會向下查找那些把“kang.o”或“yul.o”做爲目標文件的時間戳。比如,“kang.o”的依賴文件爲:“kang.c”、“kang.h”、“head.h”。如果這些文件中任何一個的時間戳比“kang.o”新,則命令“gcc –Wall –O -g –c kang.c -o kang.o”將會執行,從而更新文件“kang.o”。在更新完“kang.o”或“yul.o”之後,make會檢查最初的“kang.o”、“yul.o”和“sunq”三個文件,只要文件“kang.o”或“yul.o”中的任比文件時間戳比“sunq”新,則第二行命令就會被執行。這樣,make就完成了自動檢查時間戳的工作,開始執行編譯工作。這也就是Make工作的基本流程。
接下來,爲了進一步簡化編輯和維護Makefile,make允許在Makefile中創建和使用變量。變量是在Makefile中定義的名字,用來代替一個文本字符串,該文本字符串稱爲該變量的值。在具體要求下,這些值可以代替目標體、依賴文件、命令以及makefile文件中其它部分。在Makefile中的變量定義有兩種方式:一種是遞歸展開方式,另一種是簡單方式。
遞歸展開方式定義的變量是在引用在該變量時進行替換的,即如果該變量包含了對其他變量的應用,則在引用該變量時一次性將內嵌的變量全部展開,雖然這種類型的變量能夠很好地完成用戶的指令,但是它也有嚴重的缺點,如不能在變量後追加內容(因爲語句:CFLAGS = $(CFLAGS) -O在變量擴展過程中可能導致無窮循環)。
爲了避免上述問題,簡單擴展型變量的值在定義處展開,並且只展開一次,因此它不包含任何對其它變量的引用,從而消除變量的嵌套引用。
遞歸展開方式的定義格式爲:VAR=var
簡單擴展方式的定義格式爲:VAR:=var
Make中的變量使用均使用格式爲:$(VAR)
|
注意 |
變量名是不包括“:”、“#”、“=”結尾空格的任何字符串。同時,變量名中包含字母、數字以及下劃線以外的情況應儘量避免,因爲它們可能在將來被賦予特別的含義。 變量名是大小寫敏感的,例如變量名“foo”、“FOO”、和“Foo”代表不同的變量。 推薦在makefile內部使用小寫字母作爲變量名,預留大寫字母作爲控制隱含規則參數或用戶重載命令選項參數的變量名。 |
下面給出了上例中用變量替換修改後的Makefile,這裏用OBJS代替kang.o和yul.o,用CC代替Gcc,用CFLAGS代替“-Wall -O –g”。這樣在以後修改時,就可以只修改變量定義,而不需要修改下面的定義實體,從而大大簡化了Makefile維護的工作量。
經變量替換後的Makefile如下所示:
OBJS = kang.o yul.o
CC = Gcc
CFLAGS = -Wall -O -g
sunq : $(OBJS)
$(CC) $(OBJS) -o sunq
kang.o : kang.c kang.h
$(CC) $(CFLAGS) -c kang.c -o kang.o
yul.o : yul.c yul.h
$(CC) $(CFLAGS) -c yul.c -o yul.o
可以看到,此處變量是以遞歸展開方式定義的。
Makefile中的變量分爲用戶自定義變量、預定義變量、自動變量及環境變量。如上例中的OBJS就是用戶自定義變量,自定義變量的值由用戶自行設定,而預定義變量和自動變量爲通常在Makefile都會出現的變量,其中部分有默認值,也就是常見的設定值,當然用戶可以對其進行修改。
預定義變量包含了常見編譯器、彙編器的名稱及其編譯選項。下表3.14列出了Makefile中常見預定義變量及其部分默認值。
表3.14 Makefile中常見預定義變量
命 令 格 式 |
含 義 |
AR |
庫文件維護程序的名稱,默認值爲ar |
AS |
彙編程序的名稱,默認值爲as |
CC |
C編譯器的名稱,默認值爲cc |
CPP |
C預編譯器的名稱,默認值爲$(CC) –E |
CXX |
C++編譯器的名稱,默認值爲g++ |
FC |
FORTRAN編譯器的名稱,默認值爲f77 |
RM |
文件刪除程序的名稱,默認值爲rm –f |
ARFLAGS |
庫文件維護程序的選項,無默認值 |
ASFLAGS |
彙編程序的選項,無默認值 |
CFLAGS |
C編譯器的選項,無默認值 |
CPPFLAGS |
C預編譯的選項,無默認值 |
CXXFLAGS |
C++編譯器的選項,無默認值 |
FFLAGS |
FORTRAN編譯器的選項,無默認值 |
可以看出,上例中的CC和CFLAGS是預定義變量,其中由於CC沒有采用默認值,因此,需要把“CC=Gcc”明確列出來。
由於常見的Gcc編譯語句中通常包含了目標文件和依賴文件,而這些文件在Makefile文件中目標體的一行已經有所體現,因此,爲了進一步簡化Makefile的編寫,就引入了自動變量。自動變量通常可以代表編譯語句中出現目標文件和依賴文件等,並且具有本地含義(即下一語句中出現的相同變量代表的是下一語句的目標文件和依賴文件)。下表3.15列出了Makefile中常見自動變量。
表3.15 Makefile中常見自動變量
命令格式 |
含 義 |
$* |
不包含擴展名的目標文件名稱 |
$+ |
所有的依賴文件,以空格分開,並以出現的先後爲序,可能包含重複的依賴文件 |
$< |
第一個依賴文件的名稱 |
$? |
所有時間戳比目標文件晚的依賴文件,並以空格分開 |
命令格式 |
含 義 |
$@ |
目標文件的完整名稱 |
$^ |
所有不重複的依賴文件,以空格分開 |
$% |
如果目標是歸檔成員,則該變量表示目標的歸檔成員名稱 |
自動變量的書寫比較難記,但是在熟練了之後會非常的方便,請讀者結合下例中的自動變量改寫的Makefile進行記憶。
OBJS = kang.o yul.o
CC = Gcc
CFLAGS = -Wall -O -g
sunq : $(OBJS)
$(CC) $^ -o $@
kang.o : kang.c kang.h
$(CC) $(CFLAGS) -c $< -o $@
yul.o : yul.c yul.h
$(CC) $(CFLAGS) -c $< -o $@
另外,在Makefile中還可以使用環境變量。使用環境變量的方法相對比較簡單,make在啓動時會自動讀取系統當前已經定義了的環境變量,並且會創建與之具有相同名稱和數值的變量。但是,如果用戶在Makefile中定義了相同名稱的變量,那麼用戶自定義變量將會覆蓋同名的環境變量。
3 Makefile規則
4 Make使用
使用make管理器非常簡單,只需在make命令的後面鍵入目標名即可建立指定的目標,如果直接運行make,則建立Makefile中的第一個目標。
此外make還有豐富的命令行選項,可以完成各種不同的功能。下表3.17列出了常用的make命令行選項。
表3.17 make的命令行選項
命令格式 |
含 義 |
-C dir |
讀入指定目錄下的Makefile |
-f file |
讀入當前目錄下的file文件作爲Makefile |
命令格式 |
含 義 |
-i |
忽略所有的命令執行錯誤 |
-I dir |
指定被包含的Makefile所在目錄 |
-n |
只打印要執行的命令,但不執行這些命令 |
-p |
顯示make變量數據庫和隱含規則 |
-s |
在執行命令時不顯示命令 |
-w |
如果make在執行過程中改變目錄,則打印當前目錄名 |
Makefile的規則是Make進行處理的依據,它包括了目標體、依賴文件及其之間的命令語句。一般的,Makefile中的一條語句就是一個規則。在上面的例子中,都顯示地指出了Makefile中的規則關係,如“$(CC) $(CFLAGS) -c $< -o $@”,但爲了簡化Makefile的編寫,make還定義了隱式規則和模式規則,下面就分別對其進行講解。
1.隱式規則
隱含規則能夠告訴make怎樣使用傳統的技術完成任務,這樣,當用戶使用它們時就不必詳細指定編譯的具體細節,而只需把目標文件列出即可。Make會自動搜索隱式規則目錄來確定如何生成目標文件。如上例就可以寫成:
OBJS = kang.o yul.o
CC = Gcc
CFLAGS = -Wall -O -g
sunq : $(OBJS)
$(CC) $^ -o $@
爲什麼可以省略後兩句呢?因爲Make的隱式規則指出:所有“.o”文件都可自動由“.c”文件使用命令“$(CC) $(CPPFLAGS) $(CFLAGS) -c file.c –o file.o”生成。這樣“kang.o”和“yul.o”就會分別調用“$(CC) $(CFLAGS) -c kang.c -o kang.o”和“$(CC) $(CFLAGS) -c yul.c -o yul.o”生成。
|
注意 |
在隱式規則只能查找到相同文件名的不同後綴名文件,如”kang.o”文件必須由”kang.c”文件生成。 |
下表3.16給出了常見的隱式規則目錄:
表3.16 Makefile中常見隱式規則目錄
對應語言後綴名 |
規 則 |
C編譯:.c變爲.o |
$(CC) –c $(CPPFLAGS) $(CFLAGS) |
C++編譯:.cc或.C變爲.o |
$(CXX) -c $(CPPFLAGS) $(CXXFLAGS) |
Pascal編譯:.p變爲.o |
$(PC) -c $(PFLAGS) |
Fortran編譯:.r變爲-o |
$(FC) -c $(FFLAGS) |
2.模式規則
模式規則是用來定義相同處理規則的多個文件的。它不同於隱式規則,隱式規則僅僅能夠用make默認的變量來進行操作,而模式規則還能引入用戶自定義變量,爲多個文件建立相同的規則,從而簡化Makefile的編寫。
模式規則的格式類似於普通規則,這個規則中的相關文件前必須用“%”標明。使用模式規則修改後的Makefile的編寫如下:
OBJS = kang.o yul.o
CC = Gcc
CFLAGS = -Wall -O -g
sunq : $(OBJS)
$(CC) $^ -o $@
%.o : %.c
$(CC) $(CFLAGS) -c $< -o $@