gun工具鏈與makefile
1 GNU的由來與發展
GNU 是由“GNU's Not Unix”所遞規定義出的首字母縮寫語。GNU計劃是由Richard Stallman在1983年9月27日公開發起的。它的目標是建立一套完全自由的作業系統。RichardStallman最早是在net.unix-wizards新聞組上公佈該訊息,並附帶一份《GNU宣言》解釋為何發起該計劃的文章,其中一個重要的理由就是要“重現當年軟體界合作互助的團結精神”。GNU工程已經開發了一個被稱為“GNU”的、對Unix向上相容的完整的自由軟體系統(free software system)。由Richard Stallman完成的最初的GNU工程的文件被稱為“GNU宣言”。
每個計算機的使用者都需要一個作業系統;如果沒有自由的作業系統,那麼如果他不求助於私有軟體,就甚至不能開始使用一臺計算機。所以自由軟體議事日程的第一項就是自由的作業系統。一個作業系統不僅僅是一個核心;它還包括編譯器、編輯器、電子郵件軟體,和許多其他東西。因此,創作一個完整的作業系統是一項十分龐大的工作。由於Unix的全域性設計已經得到認證並且廣泛流傳,所以GNU開發者們決定使作業系統與Unix相容。同時這種相容性使Unix的使用者可以容易地轉移到GNU上來。
在1991年Linux的第一個版本公開發行時,GNU計劃已經完成除作業系統核心之外的大部分軟體,比如GNU Bash,GCC等等。於是Linus
GCC(GNU CompilerCollection)是GNU組織開發的一個編譯器。目前支援的語言有 C、C++、Objective-C、Fortran、Java和Ada等。
自由軟體可以走多遠?這沒有限制,除非諸如版權法之類的法律完全地禁止自由軟體。最終的目的是,讓自由軟體完成計算機使用者希望完成的所有工作--從而導致自由軟體的過時。
2 編譯器
2.2.1 GCC簡介
Linux系統下的GCC是GNU推出的功能強大、效能優越的多平臺編譯器,是GNU的代表作品之一。GCC是可以在多種硬體平臺上編譯出可執行程式的超級編譯器,其執行效率與一般的編譯器相比平均效率要高
最初,GCC只是一個C語言編譯器,當時是“GNU C Compiler”的英文縮寫。隨著眾多開發者的加入和GCC自身的發展,如今的GCC已經是一個包含眾多語言的編譯器了,其中包括 C,C++,Ada,Object-C和Java等。所以,GCC的全稱也由原來的“GNU CCompiler”演變為現在的“GNU Compiler Collection”,即GNU編譯器家族的意思。
2.2.2 GCC特點
GCC不僅是GNU/Linux上的標準編譯器,而且它也是嵌入式系統開發的標準編譯器,這是因為GCC支援各種不同的目標架構。本書將專注於FPGA平臺的嵌入式系統開發,其中的軟體部分執行在Microblaze或者PowerPC處理器上,為了使我們的應用程式能夠執行在不同的目標機上,我們使用交叉編譯工具對程式進行交叉編譯。所謂交叉編譯就是在某個主機平臺上(比如PC上)編譯出可在其他平臺上(比如ARM上)執行程式碼的過程。GCC提供了40種不同的結構體系。其中包括X86,RS6000,Arm,PowerPC等等,使用者可以根據實際的專案平臺來進行應用程式的開發。
GCC編譯器能將C、C++語言源程式、彙編程式和目標程式編譯、連結成可執行檔案,如果沒有給出可執行檔案的名字,GCC將生成一個名為a.out的檔案。在Linux系統中,可執行檔案沒有統一的字尾,系統從檔案的屬性來區分可執行檔案和不可執行檔案。而GCC則通過字尾來區別輸入檔案的類別,下面我們來介紹GCC所遵循的部分約定規則:
.c為字尾的檔案,C語言原始碼檔案;
.a為字尾的檔案,是由目標檔案構成的檔案庫檔案;
.C、.cc或.cxx 為字尾的檔案,是C++原始碼檔案;
.h為字尾的檔案,是程式所包含的標頭檔案;
.i 為字尾的檔案,是已經預處理過的C原始碼檔案;
.ii為字尾的檔案,是已經預處理過的C++原始碼檔案;
.m為字尾的檔案,是Objective-C原始碼檔案;
.o為字尾的檔案,是編譯後的目標檔案;
.s為字尾的檔案,是組合語言原始碼檔案;
.S為字尾的檔案,是經過預編譯的彙編語言原始碼檔案。
2.2.3 GCC執行過程
雖然我們稱GCC是C語言的編譯器,但使用GCC由C語言原始碼檔案生成可執行檔案的過程不僅僅是編譯的過程,而是要經歷四個相互關聯的步驟∶預處理(也稱預編譯,Preprocessing)、編譯(Compilation)、彙編(Assembly)和連結(Linking),如圖2-1所示。在對程式進行開發的過程中,我們可以通過新增引數對程式單獨執行其中的某個過程。
圖2-1 GCC執行過程
命令GCC首先呼叫cpp進行預處理,在預處理過程中,對原始碼檔案中的檔案包含(include)、預編譯語句(如巨集定義define等)進行分析。接著呼叫cc1或g++進行編譯,這個階段根據輸入檔案生成以.o為字尾的目標檔案。彙編過程是針對組合語言的步驟,呼叫as進行工作,一般來講,.s為字尾的組合語言檔案經過預編譯和彙編之後都生成以.o為字尾的目標檔案。當所有的目標檔案都生成之後,GCC就呼叫ld命令來完成最後的關鍵性工作,這個階段就是連結,當然,也可以使用GCC命令直接完成連結功能。在連結階段,所有的目標檔案被安排在可執行程式中的恰當的位置,同時,該程式所呼叫到的庫函式也從各自所在的檔案庫中連到合適的地方。
2.2.4 GCC基本用法與選項
在使用GCC編譯器的時候,我們必須給出一系列必要的呼叫引數和檔名稱。GCC編譯器的呼叫引數大約有100多個,但其中多數引數很少會用到,所以這裡只介紹其中最基本、最常用的引數。
GCC最基本的用法是∶gcc [options] [filenames] ,其中options就是編譯器所需要的引數,filenames給出相關的檔名稱,表2-1列出了常用引數的意義。
選項 | 解釋 |
-ansi | 只支援 ANSI 標準的 C 語法 |
-c | 只編譯並生成目標檔案 |
-DMACRO | 以字串“1”定義 MACRO 巨集 |
-DMACRO=DEFN | 以字串“DEFN”定義 MACRO 巨集 |
-E | 只執行 C 預編譯器 |
-g | 生成除錯資訊 |
-IDIRECTORY | 指定額外的標頭檔案搜尋路徑DIRECTORY |
-LDIRECTORY | 指定額外的函式庫搜尋路徑DIRECTORY |
-lLIBRARY | 連線時搜尋指定的函式庫LIBRARY |
-o FILE | 生成指定的輸出檔案 |
-O0 | 不進行優化處理 |
-O 或 -O1 | 優化生成程式碼 |
-O2 | 進一步優化 |
-O3 | 比 -O2 更進一步優化,包括 inline 函式 |
-static | 禁止使用共享連線 |
-w | 不生成任何警告資訊 |
-Wall | 生成所有警告資訊 |
表2-1 GCC常用引數
上面我們簡要介紹了GCC編譯器最常用的功能和主要引數選項,更為詳盡的資料可以參考http://gcc.gnu.org/。 假定我們有一個程式名為test.c的C語言原始碼檔案,要生成一個可執行檔案,最簡單的辦法就是:
gcc test.c
這時,預編譯、編譯連結一次完成,生成一個系統預設的名為a.out的可執行檔案,對於稍為複雜的情況,比如有多個原始碼檔案、需要連結檔案庫或者有其他比較特別的要求,就要給定適當的呼叫選項引數。再看一個簡單的例子。
整個原始碼程式由兩個檔案test1.c 和test2.c組成,程式中使用了系統提供的數學庫,同時希望給出的可執行檔案為test,這時的編譯命令可以是∶
gcc test1.c test2.c -lm-o test
其中,-lm表示連結系統的數學庫libm.a。
2.2.5 Gdb偵錯程式
除錯是所有程式設計師都會面臨的問題。如何提高程式設計師的除錯效率,更好更快的定位程式中的問題從而加快程式開發的進度,是大家共同面對的。就如讀者熟知的Windows下的一些除錯工具,如VC自帶的如設定斷點、單步跟蹤等,都受到了廣大使用者的讚賞。那麼,在Linux下有什麼很好的除錯工具呢?
Gdb偵錯程式是一款GNU開發組織併發布的UNIX/Linux下的程式除錯工具。雖然,它沒有圖形化的友好介面,但是它強大的功能也足以與微軟的VC工具等媲美。
首先,開啟Linux下的編輯器Vi或者Emacs,編輯如下程式碼。
/*test.c*/
#include <stdio.h>
int sum(int m);
int main()
{
int i,n=0;
sum(50);
for(i=1; i<=50; i++)
{
n += i;
}
printf("The sum of 1-50 is %d \n", n );
}
int sum(int m)
{
int i,n=0;
for(i=1; i<=m;i++)
n += i;
printf("The sum of 1-m is %d\n", n);
}
在儲存退出後首先使用Gcc對test.c進行編譯,注意一定要加上選項”-g”,這樣編譯出的可執行程式碼中才包含除錯資訊,否則之後Gdb無法載入該可執行檔案。
[[email protected]]# gcc -g test.c -o test
雖然這段程式沒有錯誤,但除錯完全正確的程式可以更加了解Gdb的使用流程。接下來就啟動Gdb進行除錯。注意,Gdb進行除錯的是可執行檔案,而不是如”.c”的原始碼,因此,需要先通過Gcc編譯生成可執行檔案才能用Gdb進行除錯。
[[email protected]]# gdb test
GNU Gdb Red HatLinux (6.3.0.0-1.21rh)
Copyright 2004Free Software Foundation, Inc.
GDB is freesoftware, covered by the GNU General Public License, and you are
welcome to changeit and/or distribute copies of it under certain conditions.
Type "showcopying" to see the conditions.
There is absolutelyno warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...Usinghost libthread_db library "/lib/libthread_db.so.1".
(gdb)
可以看出,在Gdb的啟動畫面中指出了Gdb的版本號、使用的庫檔案等資訊,接下來就進入了由“(gdb)”開頭的命令列介面了。
(1)檢視檔案
在Gdb中鍵入”l”(list)就可以檢視所載入的檔案,如下所示:
(Gdb) l
1 #include <stdio.h>
2 int sum(int m);
3 int main()
4 {
5 int i,n=0;
6 sum(50);
7 for(i=1; i<=50; i++)
8 {
9 n += i;
10 }
(Gdb) l
11 printf("The sum of 1~50 is %d \n", n );
12
13 }
14 int sum(int m)
15 {
16 int i,n=0;
17 for(i=1; i<=m;i++)
18 n += i;
19 printf("The sum of 1~m is = %d\n", n);
20 }
可以看出,Gdb列出的原始碼中明確地給出了對應的行號,這樣就可以大大地方便程式碼的定位。
(2)設定斷點
設定斷點是除錯程式中是一個非常重要的手段,它可以使程式到一定位置暫停它的執行。因此,程式設計師在該位置處可以方便地檢視變數的值、堆疊情況等,從而找出程式碼的癥結所在。
在Gdb中設定斷點非常簡單,只需在”b”後加入對應的行號即可(這是最常用的方式,另外還有其他方式設定斷點)。如下所示:
(Gdb) b 6
Breakpoint 1 at0x804846d: file test.c, line 6.
要注意的是,在Gdb中利用行號設定斷點是指程式碼執行到對應行之前將其停止,如上例中,程式碼執行到第五行之前暫停(並沒有執行第五行)。
(3)檢視斷點情況
在設定完斷點之後,使用者可以鍵入”info b”來檢視設定斷點情況,在Gdb中可以設定多個斷點。
(Gdb) info b
NumType Disp EnbAddress What
1 breakpoint keep y 0x0804846d in main attest.c:6
(4)執行程式碼
接下來就可執行程式碼了,Gdb預設從首行開始執行程式碼,可鍵入”r”(run)即可(若想從程式中指定行開始執行,可在r後面加上行號)。
(Gdb) r
Starting program:/root/workplace/Gdb/test
Reading symbols from shared objectread from target memory...done.
Loaded systemsupplied DSO at 0x5fb000
Breakpoint 1,main () at test.c:6
6 sum(50);
可以看到,程式執行到斷點處就停止了。
(5)檢視變數值
在程式停止執行之後,程式設計師所要做的工作是檢視斷點處的相關變數值。在Gdb中只需鍵入”p”+變數值即可,如下所示:
(Gdb) p n
$1 = 0
(Gdb) p i
$2 = 134518440
在此處,為什麼變數”i”的值為如此奇怪的一個數字呢?原因就在於程式是在斷點設定的對應行之前停止的,那麼在此時,並沒有把”i”的數值賦為零,而只是一個隨機的數字。但變數”n”是在第四行賦值的,故在此時已經為零。
(6)單步執行
單步執行可以使用命令”n”(next)或”s”(step),它們之間的區別在於:若有函式呼叫的時候,”s”會進入該函式而”n”不會進入該函式。因此,”s”就類似於VC等工具中的”step in”,”n”類似與VC等工具中的”stepover”。它們的使用如下所示:
(Gdb) n
The sum of 1-m is 1275
7 for(i=1; i<=50; i++)
(Gdb) s
sum (m=50) at test.c:16
16 int i,n=0;
可見,使用”n”後,程式顯示函式sum的執行結果並向下執行,而使用”s”後則進入到sum函式之中單步執行。
(7)恢復程式執行
在檢視完所需變數及堆疊情況後,就可以使用命令”c”(continue)恢復程序的正常運行了。這時,它會把剩餘還未執行的程式執行完,並顯示剩餘程式中的執行結果。以下是之前使用”n”命令恢復後的執行結果:
(Gdb) c
Continuing.
The sum of 1-50 is :1275
Program exited with code 031.
可以看出,程式在執行完後退出,之後程式處於“停止狀態”。
2.3 自動編譯
2.3.1 Make工程管理
到此為止,我們已經瞭解瞭如何在Linux下使用編輯器編寫程式碼,如何使用Gcc把程式碼編譯成可執行檔案,還學習瞭如何使用Gdb來除錯程式,那麼,所有的工作看似已經完成了,為什麼還需要Make這個工程管理器呢?
所謂工程管理器,顧名思義,是指管理較多的檔案的。可以試想一下,有一個上百個檔案的程式碼構成的專案,如果其中只有一個或少數幾個檔案進行了修改,按照之前所學的Gcc編譯工具,就不得不把這所有的檔案重新編譯一遍,因為編譯器並不知道哪些檔案是最近更新的,而只知道需要包含這些檔案才能把原始碼編譯成可執行檔案。於是,程式設計師就不能不再重新輸入數目如此龐大的檔名以完成最後的編譯工作。
但是,仔細回想一下本書在2.2.3節中所闡述的編譯過程,編譯過程是分為編譯、彙編、連結不同階段的,其中編譯階段僅檢查語法錯誤以及函式與變數的宣告是否正確聲明瞭,在連結階段則主要完成是函式連結和全域性變數的連結。因此,那些沒有改動的原始碼根本不需要重新編譯,而只要把它們重新連結進去就可以了。所以,人們就希望有一個工程管理器能夠自動識別更新了的檔案程式碼,同時又不需要重複輸入冗長的命令列,這樣,Make工程管理器也就應運而生了。
實際上,Make工程管理器也就是個“自動編譯管理器”,這裡的“自動”是指它能夠根據檔案時間戳自動發現更新過的檔案而減少編譯的工作量,同時,它通過讀入Makefile檔案的內容來執行大量的編譯工作。使用者只需編寫一次簡單的編譯語句就可以了。它大大提高了實際專案的工作效率,而且幾乎所有Linux下的專案程式設計均會涉及到它。
2.3.2 Makefile結構
makefile描述了整個工程的編譯規則,通過make命令自動化編譯。
Make是一個解釋makefile 中指令的命令工具,大多數的IDE都有這個命令,比如:
• Delphi的make,
• Visual C++的nmake
• Linux下GNU的make
Makefile是Make讀入的惟一配置檔案,因此本節的內容實際就是講述Makefile的編寫規則。在一個Makefile中通常包含如下內容:
· target:是一個目標檔案,可以是ObjectFile,也可以是可執行檔案,還可以是一個標籤(Label);
· prerequisites:要生成那個target所需要的檔案或是目標;
· Command:make需要執行的命令 。
它的格式為:
Target:prerequisites
Command
例如,有兩個檔案分別為hello.c和hello.h,建立的目標體為hello.o,執行的命令為gcc編譯指令:gcc –c hello.c,那麼,對應的Makefile就可以寫為:
hello.o: hello.c hello.h
gcc –c hello.c –ohello.o
接著就可以使用make了。使用make的格式為:make target,這樣make就會自動讀入Makefile(也可以是首字母小寫makefile)並執行對應target的command語句,並會找到相應的依賴檔案。如下所示:
[[email protected] makefile]# make hello.o
gcc –c hello.c –o hello.o
[[email protected] makefile]# ls
hello.c hello.h hello.o Makefile
可以看到,Makefile執行了“hello.o”對應的命令語句,並生成了“hello.o”目標體。
注意:每一個命令的第一個字元必須是“tab”鍵,不可使用8個“space”鍵替代,否則make會顯示出錯資訊
2.3.3 makefile變數
上面示例的Makefile在實際中是幾乎不存在的,因為它過於簡單,僅包含兩個檔案和一個命令,在這種情況下完全不必要編寫Makefile而只需在Shell中直接輸入即可,在實際中使用的Makefile往往是包含很多的檔案和命令的,這也是Makefile產生的原因。下面就可給出稍微複雜一些的Makefile(2個頭檔案,5個C檔案)進行講解:
edit : main.okbd.o cc -o edit main.o kbd.o main.o : main.c defs.h cc -c main.ckbd.o : kbd.c defs.h command.h cc -c kbd.cclean : rm edit main.o kbd.o
在這個Makefile中有三個目標體(target),分別為edit、main.o和kbd.o,其中第一個目標體的依賴檔案就是後兩個目標體。如果使用者使用命令“makeedit”,則make管理器就是找到edit目標體開始執行。
這時,make會自動檢查相關檔案的時間戳。首先,在檢查“main.o”、“kbd.o”和“edit”三個檔案的時間戳之前,它會向下查詢那些把“main.o”或“kbd.o”做為目標檔案的時間戳。比如,“main.o”的依賴檔案為:“main.c”、“defs.h”。如果這些檔案中任何一個的時間戳比“main.o”新,則命令“gcc –Wall –O -g –c main.c -omain.o”將會執行,從而更新檔案“main.o”。在更新完“main.o”或“kbd.o”之後,make會檢查最初的“main.o”、“kbd.o”和“edit”三個檔案,只要檔案“main.o”或“kbd.o”中的任比檔案時間戳比“edit”新,則第二行命令就會被執行。這樣,make就完成了自動檢查時間戳的工作,開始執行編譯工作。這也就是Make工作的基本流程。
接下來,為了進一步簡化編輯和維護Makefile,make允許在Makefile中建立和使用變數。變數是在Makefile中定義的名字,用來代替一個文字字串,該文字字串稱為該變數的值。在具體要求下,這些值可以代替目標體、依賴檔案、命令以及makefile檔案中其它部分。在Makefile中的變數定義有兩種方式:一種是遞迴展開方式,另一種是簡單方式。
遞迴展開方式定義的變數是在引用在該變數時進行替換的,即如果該變數包含了對其他變數的應用,則在引用該變數時一次性將內嵌的變數全部展開,雖然這種型別的變數能夠很好地完成使用者的指令,但是它也有嚴重的缺點,如不能在變數後追加內容(因為語句:CFLAGS =$(CFLAGS) -O在變數擴充套件過程中可能導致無窮迴圈)。
為了避免上述問題,簡單擴充套件型變數的值在定義處展開,並且只展開一次,因此它不包含任何對其它變數的引用,從而消除變數的巢狀引用。
遞迴展開方式的定義格式為:VAR=var
簡單擴充套件方式的定義格式為:VAR:=var
Make中的變數使用均使用格式為:$(VAR)
變數名是不包括“:”、“#”、“=”結尾空格的任何字串。同時,變數名中包含字母、數字以及下劃線以外的情況應儘量避免,因為它們可能在將來被賦予特別的含義。
變數名是大小寫敏感的,例如變數名“foo”、“FOO”、和“Foo”代表不同的變數。
推薦在makefile內部使用小寫字母作為變數名,預留大寫字母作為控制隱含規則引數或使用者過載命令選項引數的變數名。
下面給出了上例中用變數替換修改後的Makefile,這裡用OBJS代替main.o和kbd.o,用CC代替Gcc,用CFLAGS代替“-Wall -O –g”。這樣在以後修改時,就可以只修改變數定義,而不需要修改下面的定義實體,從而大大簡化了Makefile維護的工作量。
經變數替換後的Makefile如下所示:
OBJS= main.o kbd.o
CC =cc
edit: $(OBJS)
$(CC) $(OBJS) -o edit
main.o : main.c defs.h
$(CC) -c main.c
kbd.o : kbd.c defs.hcommand.h
$(CC) -c kbd.c
可以看到,此處變數是以遞迴展開方式定義的。
Makefile中的變數分為使用者自定義變數、預定義變數、自動變數及環境變數。如上例中的OBJS就是使用者自定義變數,自定義變數的值由使用者自行設定,而預定義變數和自動變數為通常在Makefile都會出現的變數,其中部分有預設值,也就是常見的設定值,當然使用者可以對其進行修改。
預定義變數包含了常見編譯器、彙編器的名稱及其編譯選項。下表2-2列出了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編譯器的選項,無預設值 |
表2-2 Makefile中常見預定義變數
可以看出,上例中的CC和CFLAGS是預定義變數,其中由於CC沒有采用預設值,因此,需要把“CC=Gcc”明確列出來。
由於常見的Gcc編譯語句中通常包含了目標檔案和依賴檔案,而這些檔案在Makefile檔案中目標體的一行已經有所體現,因此,為了進一步簡化Makefile的編寫,就引入了自動變數。自動變數通常可以代表編譯語句中出現目標檔案和依賴檔案等,並且具有本地含義(即下一語句中出現的相同變數代表的是下一語句的目標檔案和依賴檔案)。下表2-3列出了Makefile中常見自動變數。
命 令 格 式 | 含 義 |
$* | 不包含副檔名的目標檔名稱 |
$+ | 所有的依賴檔案,以空格分開,並以出現的先後為序,可能包含重複的依賴檔案 |
$< | 第一個依賴檔案的名稱 |
$? | 所有時間戳比目標檔案晚的依賴檔案,並以空格分開 |
目標檔案的完整名稱 | |
$^ | 所有不重複的依賴檔案,以空格分開 |
$% | 如果目標是歸檔成員,則該變量表示目標的歸檔成員名稱 |
表2-3 Makefile中常見自動變數
自動變數的書寫比較難記,但是在熟練了之後會非常的方便,請讀者結合下例中的自動變數改寫的Makefile進行記憶。
OBJS= main.o kbd.o
CC =cc
edit : $(OBJS)
$(CC) $^ -o [email protected]
main.o : main.c defs.h
$(CC) -c $< -o [email protected]
kbd.o : kbd.c defs.hcommand.h
$(CC) -c $< -o [email protected]
另外,在Makefile中還可以使用環境變數。使用環境變數的方法相對比較簡單,make在啟動時會自動讀取系統當前已經定義了的環境變數,並且會建立與之具有相同名稱和數值的變數。但是,如果使用者在Makefile中定義了相同名稱的變數,那麼使用者自定義變數將會覆蓋同名的環境變數。
2.3.4 makefile規則
Makefile的規則是Make進行處理的依據,它包括了目標體、依賴檔案及其之間的命令語句。一般的,Makefile中的一條語句就是一個規則。在上面的例子中,都顯示地指出了Makefile中的規則關係,如“$(CC) $(CFLAGS) -c $< [email protected]”,但為了簡化Makefile的編寫,make還定義了隱式規則和模式規則,下面就分別對其進行講解。
1.隱式規則
隱含規則能夠告訴make怎樣使用傳統的技術完成任務,這樣,當用戶使用它們時就不必詳細指定編譯的具體細節,而只需把目標檔案列出即可。Make會自動搜尋隱式規則目錄來確定如何生成目標檔案。如上例就可以寫成:
OBJS = main.okbd.o
CC = cc
edit: $(OBJS)
$(CC) $^ -o [email protected]
main.o : main.cdefs.h
kbd.o : kbd.cdefs.h command.h
為什麼可以省略後兩句呢?Make具有自動推導檔案以及檔案依賴關係後面的命令,沒必要在每一個.o檔案後寫同名的.c檔案,以及編譯命令,此既是make的“隱式規則”。
下表2-4給出了常見的隱式規則目錄:
對應語言字尾名 | 規 則 |
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-4 Makefile中常見隱式規則目錄
2.模式規則
模式規則是用來定義相同處理規則的多個檔案的。它不同於隱式規則,隱式規則僅僅能夠用make預設的變數來進行操作,而模式規則還能引入使用者自定義變數,為多個檔案建立相同的規則,從而簡化Makefile的編寫。
模式規則的格式類似於普通規則,這個規則中的相關檔案前必須用“%”標明。使用模式規則修改後的Makefile的編寫如下:
OBJS = main.o kbd.o
CC = cc
edit: $(OBJS)
$(CC) $^ -o [email protected]
%.o : %.c
$(CC) -c $< -o [email protected]
2.3.5 makefile規則
使用make管理器非常簡單,只需在make命令的後面鍵入目標名即可建立指定的目標,如果直接執行make,則建立Makefile中的第一個目標。
此外make還有豐富的命令列選項,可以完成各種不同的功能。下表2-5列出了常用的make命令列選項。
命令格式 | 含 義 |
-C dir | 讀入指定目錄下的Makefile |
-f file | 讀入當前目錄下的file檔案作為Makefile |
-i | 忽略所有的命令執行錯誤 |
-I dir | 指定被包含的Makefile所在目錄 |
-n | 只打印要執行的命令,但不執行這些命令 |
-p | 顯示make變數資料庫和隱含規則 |
-s | 在執行命令時不顯示命令 |
-w | 如果make在執行過程中改變目錄,則列印當前目錄名 |
表2-5 make的命令列選項
2.3.6 使用autotools
Makefile可以幫助make完成它的使命,但要承認的是,編寫Makefile確實不是一件輕鬆的事,尤其對於一個較大的專案而言更是如此。那麼,有沒有一種輕鬆的手段生成Makefile而同時又能讓使用者享受make的優越性呢?本節要講的autotools系列工具正是為此而設的,它只需使用者輸入簡單的目標檔案、依賴檔案、檔案目錄等就可以輕鬆地生成Makefile了,這無疑是廣大使用者的所希望的。另外,這些工具還可以完成系統配置資訊的收集,從而可以方便地處理各種移植性的問題。也正是基於此,現在Linux上的軟體開發一般都用autotools來製作Makefile。
autotools是系列工具,讀者首先要確認系統是否裝了以下工具(可以用which命令進行檢視)。
aclocal
autoscan
autoconf
autoheader
automake
使用autotools主要就是利用各個工具的指令碼檔案以生成最後的Makefile。其總體流程是這樣的:
使用aclocal生成一個“aclocal.m4”檔案,該檔案主要處理本地的巨集定義;
改寫“configure.scan”檔案,並將其重新命名為“configure.in”,並使用autoconf檔案生成configure檔案。
使用者不再需要定製不同的規則,而只需要輸入簡單的檔案及目錄名即可,這樣就大大方便了使用者的使用。下面的圖2-1總結了上述過程:
圖2-1 autotools生成Makefile流程圖