- 嵌入式Linux C語言程序設計基礎教程(微課版)
- 華清遠見嵌入式學院 劉洪濤 苗德行主編
- 4088字
- 2021-01-08 20:44:08
1.5 make工程管理器
前面幾節主要介紹如何在Linux環境下使用文本編輯器,如何使用GCC編譯出可執行文件,以及如何使用GDB來調試程序。既然所有的工作都已經完成了,為什么還需要make這個工程管理器呢?
工程管理器可以用來管理較多的文件。讀者可以試想一下:一個由上百個源文件構成的項目,如果其中只有一個或少數幾個文件進行了修改,按照之前所學的GCC的用法,就不得不把所有的文件重新編譯一遍。原因就在于編譯器并不知道哪些文件是最近更新的,所以,程序員就不得不處理所有的文件來完成重新編譯工作。
顯然,開發人員需要一個能夠自動識別出那些被更新的代碼文件并實現整個工程自動編譯的工具。
實際上,make就是一個自動編譯管理器,能夠根據文件時間戳自動發現更新過的文件從而減少編譯的工作量。同時,它通過讀入 Makefile文件的內容來執行大量的編譯工作,用戶只需編寫一次簡單的編譯語句即可。它大大提高了項目開發和維護

Makefile
的工作效率,幾乎所有嵌入式 Linux 下的項目編程均會涉及 make 管理器,希望讀者能夠認真學習本節內容。
1.5.1 Makefile基本結構
Makefile用來告訴 make如何編譯和鏈接一個程序,它是 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命令時會出錯。
上面實例中的Makefile在實際中是幾乎不存在的,因為它過于簡單,僅包含兩個文件和一個命令,在這種情況下完全不需要編寫 Makefile,只需在 Shell 中直接輸入命令即可。在實際中使用的 Makefile往往是包含很多命令的,一個項目也會包含多個Makefile。
下面就對較復雜的Makefile進行講解。以下這個工程包含有3個頭文件和8個C文件,其Makefile如下:
edit : main.o kbd.o command.o display.o insert.o search.o files.o utils.o
gcc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
main.o : main.c defs.h
gcc -c main.c–o main.o
kbd.o : kbd.c defs.h command.h
gcc -c kbd.c–o kbd.o
command.o : command.c defs.h command.h
gcc -c command.c–o command.o
display.o : display.c defs.h buffer.h
gcc -c display.c–o display.o
insert.o : insert.c defs.h buffer.h
gcc -c insert.c–o insert.o
search.o : search.c defs.h buffer.h
gcc -c search.c–o search.o
files.o : files.c defs.h buffer.h command.h
gcc -c files.c–o files.o
utils.o : utils.c defs.h
gcc -c utils.c–o utils.o
clean :
rm edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
這里的反斜杠“\”是換行符的意思,用于增加Makefile的可讀性。讀者可以把這些內容保存在文件名為“Makefile”或“makefile”的文件中,然后在該目錄下直接輸入命令“make”就可以生成可執行文件“edit”。如果想要刪除可執行文件和所有的中間目標文件,只需要簡單地執行一下“make clean”即可。
在這個“makefile”中,目標文件(target)包含以下內容:可執行文件“edit”和中間目標文件“*.o”,依賴文件(dependency_file)就是冒號后面的那些“*.c”文件和“*.h”文件。
每一個“.o”文件都有一組依賴文件,而這些“.o”文件又是可執行文件“edit”的依賴文件。依賴關系表明目標文件是由哪些文件生成的。換言之,目標文件是由哪些文件更新的。
在定義好依賴關系后,后面的一行命令定義了如何生成目標文件。請讀者注意,這些命令都是以一個“Tab”鍵作為開頭的。
值得注意的是,make工程管理器并不關心命令是如何工作的,它只負責執行用戶事先定義好的命令。同時,make還會比較目標文件和依賴文件的最后修改日期,如果依賴文件的日期比目標文件的日期新,或者目標文件并不存在,那么,make就會執行后續定義的命令。
這里要說明一點,clean不是一個文件,它只不過是一個動作名稱,也可稱其為標簽,不依賴于其他任何文件。
若用戶想要執行其后的命令,就要在make命令后顯式地指出這個標簽的名字。這個方法非常有用,通常用戶可以在一個Makefile中定義一些和編譯無關的命令,比如程序的打包、備份或刪除等。
1.5.2 Makefile變量
為了進一步簡化 Makefile 的編寫和維護,make 允許在 Makefile 中創建和使用變量。變量是在Makefile中定義的名字,用來代替一個文本字符串,該文本字符串稱為該變量的值。
變量的值可以用來代替目標體、依賴文件、命令以及Makefile文件中的其他部分。在Makefile中的變量定義有兩種方式:一種是遞歸展開方式,另一種是簡單擴展方式。
遞歸展開方式定義的變量是在引用該變量時進行替換的,即如果該變量包含了對其他變量的引用,則在引用該變量時一次性將內嵌的變量全部展開。雖然這種類型的變量能夠很好地完成用戶的指令,但是它也有嚴重的缺點,如不能在變量后追加內容,因為語句“CFLAGS = $(CFLAGS) –O”在變量擴展過程中可能導致無窮循環。
為了避免上述問題,簡單擴展型變量的值在定義處展開,并且只展開一次,因此它不包含任何對其他變量的引用,從而消除了變量的嵌套引用。
遞歸展開方式的定義格式為VAR=var。
簡單擴展方式的定義格式為VAR:=var。
Make中的變量使用格式為$(VAR)。
提示
變量名是不包括“:”,“#”,“=”,結尾空格的任何字符串。同時,變量名中包含字母、數字以及下劃線以外的情況應盡量避免,因為它們可能在將來被賦予特別的含義。
變量名是大小寫敏感的,例如變量名“foo”“FOO”和“Foo”代表不同的變量。
推薦在Makefile內部使用小寫字母作為變量名,預留大寫字母作為控制隱含規則參數或用戶重載命令選項參數的變量名。
在上面的例子中,先來看看edit這個規則。
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
gcc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
讀者可以看到“.o”文件的字符串被重復了兩次,如果在工程需要加入一個新的“.o”文件,那么用戶需要在這兩處分別加入(其實應該是有3處,另外一處在clean中)。
當然,這個實例的Makefile并不復雜,所以在這兩處分別添加也沒有太多的工作量。但如果Makefile變得復雜,用戶就很有可能會忽略一個需要加入的地方,從而導致編譯失敗。所以,為了使Makefile易維護,推薦在Makefile中盡量使用變量這種形式。
這樣,用戶在這個實例中就可以按以下的方式來定義變量。
OBJS = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
這里是以遞歸展開的方式來進行定義的。在此之后,用戶就可以很方便地在Makefile中以“$(OBJS)”的方式來使用這個變量了,于是改良版Makefile就變為如下形式。
OBJS = main.O kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(OBJS)
gcc -o edit $(OBJS)
main.o : main.c defs.h
gcc -c main.c–o main.o
kbd.o : kbd.c defs.h command.h
gcc -c kbd.c–o kbd.o
command.o : command.c defs.h command.h
gcc -c command.c–o command.o
display.o : display.c defs.h buffer.h
gcc -c display.c–o display.o
insert.o : insert.c defs.h buffer.h
gcc -c insert.c–o insert.o
search.o : search.c defs.h buffer.h
gcc -c search.c–o search.o
files.o : files.c defs.h buffer.h command.h
gcc -c files.c–o files.o
utils.o : utils.c defs.h
gcc -c utils.c–o utils.o
clean :
rm edit $(OBJS)
可以看到,如果這時又有新的“.o”文件需要加入,用戶只需簡單地修改一下變量OBJS的值就可以了。
Makefile 中的變量分為用戶自定義變量、預定義變量、自動變量及環境變量。如上例中的 OBJS 就屬于用戶自定義變量,其值由用戶自行設定。預定義變量和自動變量無需定義就可以在Makefile中使用,其中部分有默認值,當然用戶也可以對其進行修改。
預定義變量包含了常見編譯器、匯編器的名稱及編譯選項,表 1-12所示為 Makefile中常見預定義變量及其部分默認值。
表1-12 Makefile中常見預定義變量

上例中的CC和CFLAGS是預定義變量,其中由于CC沒有采用默認值,因此,需要把“CC=gcc”明確列出來。
由于常見的 GCC 編譯語句中通常包含了目標文件和依賴文件,而這些文件在 Makefile 文件中目標體的一行已經有所體現,因此,為了進一步簡化Makefile的編寫,引入了自動變量。
自動變量通常可以代表編譯語句中出現的目標文件和依賴文件等,并且具有本地含義(即下一語句中出現的相同變量代表的是下一語句的目標文件和依賴文件),表1-13所示為Makefile中常見自動變量。
表1-13 Makefile中常見自動變量

自動變量的書寫比較難記,但是在熟練了之后會非常方便,請讀者結合下例中的自動變量改寫的Makefile進行記憶。
OBJS = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
CC = gcc
CFLAGS = -Wall -O -g
edit : $(OBJS)
$(CC) $^ -o $@
main.o : main.c defs.h
$(CC) $(CFLAGS) -c $< -o $@
kbd.o : kbd.c defs.h command.h
$(CC) $(CFLAGS) -c $< -o $@
command.o : command.c defs.h command.h
$(CC) $(CFLAGS) -c $< -o $@
display.o : display.c defs.h buffer.h
$(CC) $(CFLAGS) -c $< -o $@
insert.o : insert.c defs.h buffer.h
$(CC) $(CFLAGS) -c $< -o $@
search.o : search.c defs.h buffer.h
$(CC) $(CFLAGS) -c $< -o $@
files.o : files.c defs.h buffer.h command.h
$(CC) $(CFLAGS) -c $< -o $@
utils.o : utils.c defs.h
$(CC) $(CFLAGS) -c $< -o $@
clean :
rm edit $(OBJS)
另外,在 Makefile 中還可以使用環境變量。使用環境變量的方法相對比較簡單,make 在啟動時會自動讀取系統當前已經定義的環境變量,并且會創建與之具有相同名稱和數值的變量。但是,如果用戶在Makefile中定義了相同名稱的變量,那么用戶自定義變量將會覆蓋同名的環境變量。
1.5.3 Makefile規則
Makefile的規則包括目標體、依賴文件及其間的命令語句,是make進行處理的依據。Makefile中的一條語句就是一個規則。
在上面的例子中顯式地指出了Makefile中的規則關系,如“$(CC) $(CFLAGS) -c $< -o $@”。為了簡化Makefile的編寫,make還定義了隱式規則和模式規則,下面就分別對其進行講解。
1.隱式規則
隱式規則能夠告訴 make 怎樣使用傳統的技術完成任務,這樣,當用戶使用它們時就不必詳細指定編譯的具體細節,而只需把目標文件列出即可。make會自動搜索隱式規則目錄來確定如何生成目標文件,如上例可以寫成如下形式。
OBJS = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
CC = gcc
CFLAGS = -Wall -O -g
edit :$(OBJS)
$(CC) $^ -o $@
main.o : main.c defs.h
kbd.o : kbd.c defs.h command.h
command.o : command.c defs.h command.h
display.o : display.c defs.h buffer.h
insert.o : insert.c defs.h buffer.h
search.o : search.c defs.h buffer.h
files.o : files.c defs.h buffer.h command.h
utils.o : utils.c defs.h
clean :
rm edit $(OBJS)
為什么可以省略“$(CC) $(CFLAGS) -c $< -o $@”這句呢?
因為make的隱式規則指出:所有“.o”文件都可自動由“.c”文件使用命令“$(CC) $(CPPFLAGS)$(CFLAGS) –c file.c–o file.o”生成。因此,Makefile就可以進一步地簡化了。
提示
在隱式規則只能查找到相同文件名的不同后綴名文件,如“kang.o”文件必須由“kang.c”文件生成。
表1-14所示為常見的隱式規則目錄。
表1-14 Makefile中常見隱式規則目錄

2.模式規則
模式規則不同于隱式規則,是用來定義相同處理規則的多個文件的,模式規則能引入用戶自定義變量,為多個文件建立相同的規則,簡化Makefile的編寫。
模式規則的格式類似于普通規則,這個規則中的相關文件前必須用“%”標明,然而在這個實例中,并不能使用這個模式規則。
1.5.4 make使用
使用make管理器非常簡單,只需在make命令的后面鍵入目標名即可建立指定的目標。如果直接運行make,則建立Makefile中的第一個目標。
此外,make還有豐富的命令行選項,可以完成各種不同的功能,表1-15所示為常用的make命令行選項。
表1-15 make的命令行選項
