Linux Make(Makefile)由淺入深的學習與示例剖析

經過長時間學習和研究linux GNU make工程管理器 ,現在把學習心得與大家分享一下,希望本文能教會您一些有用的東西。

make工具,是所有想在Linux/Unix系統上編程的用戶都需要且必須掌握的工具。如果您寫的程序沒有用到make工具,則說明您寫的程序僅僅是個人練習小程序,稱不上有實用價值的程序,因此我們必須學習、掌握並靈活運用它。

在Linux/UNIX 系統中,習慣使用 Makefile或makfile 文件作爲make命令目標文件。 Make工具最主要也是最基本的功能就是通過makefile文件來描述源程序之間的相互依賴關係並自動維護編譯工作。而makefile 文件需要按照某種語法進行編寫,文件中需要說明如何編譯各個源文件並連接生成可執行文件,並要求定義源文件之間的依賴關係。

一、多文件編譯的總體結構

如下圖所示, 本示例 共包含 float類型加法、加法頭函數、int類型加法、main主函數、float類型減法、減法頭函數、int類型減法

主函數

 

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. #include "add.h"  
  2. #include "sub.h"  
  3. #include <stdio.h>  
  4. int main()  
  5. {  
  6.         int x, y;  
  7.         float a, b;  
  8.         x=5;  
  9.         y=2;  
  10.         a=5.5;  
  11.         b=2.2;  
  12.         printf("%d + %d = %d/n", x, y, add_int(x, y));  
  13.         printf("%3.1f + %3.1f = %3.1f/n", a, b, add_float(a, b));  
  14.         printf("%d - %d = %d/n", x, y, sub_int(x, y));  
  15.         printf("%3.1f - %3.1f = %3.1f/n", a, b, sub_float(a, b));  
  16.         return 0;  
  17. }  

 

加法頭函數

 

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. /* add.h */  
  2. #ifndef _ADD_H_  
  3. #define _ADD_H_  
  4. extern int add_int(int x, int y);  
  5. extern float add_float(float x, float y);  
  6. #endif  

 

int類型加法

 

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. /* add_int.c */  
  2. int add_int(int x, int y)  
  3. {  
  4.         return x+y;  
  5. }  

 

float類型加法

 

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. /* add_float.c */  
  2. float add_float(float x, float y)  
  3. {  
  4.         return x+y;  
  5. }  
  6. ~    

 

減法頭函數

 

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. /* sub.h */  
  2. #ifndef _SUB_H_  
  3. #define _SUB_H_  
  4. extern int sub_int(int x, int y);  
  5. extern float sub_float(float x, float y);  
  6. #endif  

 

int類型減法

 

  1. /* sub_int.c */  
  2. int sub_int(int x, int y)  
  3. {  
  4.         return x-y;  
  5. }  

 

float類型減法

 

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. /* sub_float.c */  
  2. float sub_float(float x, float y)  
  3. {  
  4.         return x-y;  
  5. }  

 

二、方法1 ( 多文件編譯 

直接在Linux的Shell環境中,分別依次利用gcc -c *.c -o *.o命令進行編譯,並鏈接生成可執行文件,具體做法如下:

依次編譯上述文件:

編譯後的結果文件:

然後,直接利用gcc -o main add_int.o add_float.o sub_int.o sub_float.o main.o命令,鏈接、編譯成目標可執行文件main

最後,輸入 ./main 運行結果

評析: 此方法遵照單文件編譯方法,過程清晰、直觀易懂;但效率很低,在編譯文件數量很大或源文件修改時,此方法效率很低,且難以維護

三、方法 2 ( 多文件編譯——使用makefile 

此方法爲了避免方法1的不足,利用Linux GNU make工程管理器,進行編譯、管理源文件。

首先,瞭解一下make和makefile。 GNU make是一個工程管理器,專門負責管理、維護較多文件的處理,實現自動化編譯。如果一個工程項目中,有成百上千個代碼源文件,若其中一個或多個文件進過修改,make就需要能夠自動識別更新了的代碼,不需要像方法1一樣逐個輸入編譯冗長的命令行,就可以完成最後的編譯工作。make執行時,自動尋找makefile(Makefile)文件,然後執行編譯工作。因此,我們需要自己編寫makefile文件(Makefile與makefile都可以直接被make命令識別,下同。但Linux區分大小寫)來管理、維護工程文件,提高實際項目的工作效率。

其次,需要注意Linux makefile(Makefile)文件的編寫規範和方法:

1、需要由make工具創建目標體target,即通常的目標文件或可執行文件

2、聲明並給出創建的目標體所依賴的文件(dependency-file)

3、編寫完成創建每個目標體時所需要執行的命令(command)

具體格式如下:

target: dependency-file1    dependency-file2    dependency-file3    ...

command

target:規劃的目標。通常是程序中間體或最後所需要生成的文件名,如 *.o或obj可執行文件的名稱。此外,target目標也可以是make執行動作的名稱,如clean等

dependency-file:規則的依賴。生成規則目標所需要的文件名列表,通常是一個目標依賴於一個或多個文件。

command:規則的命令。make程序所執行的的動作,可以爲shell命令或者在shell下執行的程序。一個規則可以有多條命令,每條命令佔一行。 在此特別需要注意的是每條命令行開始必須以Tab字符縮進開始,Tab縮進字符會告訴make命令此行是一個命令行,make按照命令完成此行相應的動作。這是在書寫makefile(Makefile)文件時最易忽視和犯錯的地方,而且大多比較隱蔽。

命令實質上市對任何一個目標的依賴文件發生變化後重建目標的動作描述。一個目標可以沒有依賴而只有動作,即只有命令,如clean。此目標只有命令,沒有依賴,主要作用是用來刪除make過程中產生的中間文件(*.o),做收尾清理工作。

最後,上面均是紙上談兵,現在我們來看具體實例,以直觀、具體、詳盡的解釋makefile文件的編寫方法和規則。

方法1可以用如下makefile文件代替,makefile編寫如下:

 

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. # target: dependency-file  
  2. main: main.o add_int.o add_float.o sub_int.o sub_float.o  
  3. # NOTE: Tab before command  
  4.         gcc -o main main.o add_int.o add_float.o sub_int.o sub_float.o  
  5. main.o: main.c add.h sub.h  
  6.         gcc -c main.c -o main.o  
  7. add_int.o: add_int.c add.h  
  8.         gcc -c add_int.c -o add_int.o  
  9. add_float.o: add_float.c add.h  
  10.         gcc -c add_float.c -o add_float.o  
  11. sub_int.o: sub_int.c sub.h  
  12.         gcc -c sub_int.c -o sub_int.o  
  13. sub_float.o: sub_float.c sub.h  
  14.         gcc -c sub_float.c -o sub_float.o  
  15. clean:  
  16.         rm -f *.o main  

 

說明:

#表示註釋,其後的在編譯預處理時,將被全部刪除不執行

gcc -c 編譯C語言源文件,編譯生成目標文件 *.o

gcc -o 定義生成文件名稱,可以爲 *.o(目標文件)和 main(可執行文件)

rm -f *.o main 強制刪去該目錄下的所有*.o 目標文件和main可執行文件

在shell命令行執行make命令

查看make執行makefile文件後的編譯結果如下:

與方法1的結果基本一致,並且直接生成了可執行文件main

最後,輸入 ./main 運行結果

此方法,與方法1運行結果,完全一致!

評析: 方法2利用makefile文件,進行項目所有文件的編譯管理,可保存、易修改,且編譯執行效率高,大大減輕了每次編譯的工作量

方法2,僅僅是最爲初級的makefile項目管理格式,現在我們將逐步對其進行優化、改進

四、方法 3 (使用變量——改進1)

在編寫makefile文件時,各部分引用變量的格式規範

1、 make變量引用不同於Linux Shell變量引用規則,而是需加括號,即 $(Var) 格式,無論 Var 是單字符變量名還是多字符變量名均可。

2、在命令行中出現的Shell變量,引用Shell的 $tmp 格式,一般爲執行命令過程中的臨時變量,不屬於makefile變量,而是Shell變量。

3、對出現在命令行中的make變量,同樣使用 $(Command) 格式來引用。

紙上得來終覺淺,絕知此事要躬行。輸入vim makefile命令,在Shell 利用vim編輯器來編寫makefile文件,具體寫法如下:

 

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. OBJ=main.o add_int.o add_float.o sub_int.o sub_float.o  
  2. make: $(OBJ)  
  3.         gcc -c main.c -o main.o  
  4. add_int.o: add_int.c add.h  
  5.         gcc -c add_int.c -o add_int.o  
  6. add_float.o: add_float.c add.h  
  7.         gcc -c add_float.c -o add_float.o  
  8. sub_int.o: sub_int.c sub.h  
  9.         gcc -c sub_int.c -o sub_int.o  
  10. sub_float.o: sub_float.c sub.h  
  11.         gcc -c sub_float.c -o sub_float.o  
  12. clean:  
  13.         rm -f $(OBJ) main  

 

然後,在shell命令行執行make命令

最後,輸入 ./main 運行結果

此方法,與方法1和方法2運行結果,完全一致!

評析: 方法3利用makefile變量,引入變量使makefile更加簡潔、清晰,便於分組、統一維護,編譯管理更加高效

五、方法 4 (使用自動推導——改進2)

編寫makefile文件,讓make命令自動推導。只要make看到了 *.o 文件,它就會自動把與之對應的 *.c 文件加到依賴文件中,並且gcc -c *.c 也會被推導出來,所以makefile就簡化啦。 此外,我們使用 $(Command) 格式,來引用命令變量。具體做法如下

首先,在Shell輸入 vim makefile ,利用VIM編輯makefile文件內容

 

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. CC=gcc  
  2. OBJ=main.o add_int.o add_float.o sub_int.o sub_float.o  
  3. make: $(OBJ)  
  4.         $(CC) -o main $(OBJ)  
  5. main.o: add.h sub.h  
  6. add_int.o: add.h  
  7. add_float.o: add.h  
  8. sub_int.o: sub.h  
  9. sub_float.o: sub.h  
  10. PHONY: clean  
  11. clean:  
  12.         rm -f $(OBJ) main  

 

然後,在shell命令行執行make命令

最後,輸入 ./main 運行結果

此方法,與方法1、方法2和方法3的運行結果,完全一致!

評析: 方法4在makefile文件中,引入參數變量和命令變量,利用make命令自動推導依賴文件,來編譯系統,高效但不太直觀,高手可用

六、方法5 (使用自動變量($^ $< $@)——改進3)

在編寫makefile文件中,有三個非常有用的變量,即分別是 $@     $^     $<    其代表的具體意義如下:

$@  : 目標文件

$^   : 所有依賴文件

$<   : 第一個依賴文件

具體使用方法如下例所示

首先,在Shell輸入 vim makefile ,利用VIM編輯makefile文件內容

 

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. CC=gcc  
  2. OBJ=main.o add_int.o add_float.o sub_int.o sub_float.o  
  3. make: $(OBJ)  
  4.         $(CC) -o $@ $^  
  5. main.o: main.c add.h sub.h  
  6.         $(CC) -c $<  
  7. add_int.o: add_int.c add.h  
  8.         $(CC) -c $<  
  9. add_float.o: add_float.c add.h  
  10.         $(CC) -c $<  
  11. sub_int.o: sub_int.c sub.h  
  12.         $(CC) -c $<  
  13. sub_float.o: sub_float.c sub.h  
  14.         $(CC) -c $<  
  15. PHONY: clean  
  16. clean:  
  17.         rm -f $(OBJ) main  

 

然後,在shell命令行執行make命令

最後,輸入 ./main 運行結果

此方法,與方法1、方法2、方法3和方法4的運行結果,完全一致!

評析: 方法5在makefile文件中,引入參數變量、命令變量和自動變量,此方法編譯系統,高效但不太直觀,特別是維護修改不便,高手可秀。

七、方法6  (使用缺省規則(..c.o:)—— 改進4)

 在依次使用了上述變量、自動推導、自動變量規則後,或許還有人認爲太複雜,想尋求更簡潔的方法,這裏我們再介紹makefile缺省規則。

 makefile的缺省規則如下:

..c.o:

gcc -c $<

這個規則表示,所有的 *.o 目標文件都是依賴於相應的 *.c 源文件的, 例如 main.o 依賴於 main.c 。 具體makefile編寫方法如下

 

·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
  1. CC=gcc  
  2. main: main.o add_int.o add_float.o sub_int.o sub_float.o  
  3.         $(CC) -o $@ $^  
  4. ..c.o:  
  5.         $(CC) -c $<  
  6. clean:  
  7.         rm -f *.o main  

 

然後,在shell命令行執行make命令

最後,輸入 ./main 運行結果

此方法,與方法1、方法2、方法3、方法4和方法5的運行結果,完全一致!

評析: 方法6在makefile文件中,引入缺省規則,是make自動推導,非常簡潔、高效,但不太直觀,特別是具體文件依賴關係不清,維護較不便。

==================================================================================

綜合評析: 以上方法,由淺入深,點評剖析,重在靈活運用。方法2直觀易懂,方法3引入變量簡潔,這兩種方法便於管理維護,推薦使用。方法4、方法5和方法6,主要是深入剖析makefile博大精深的編寫使用方法,在具體項目管理實踐中,可以選擇借鑑使用,適合內功深厚者。

以上示例程序,均已測試並運行通過 ,具體測試編譯環境如下:

Linux系統: Red Hat Linux Server 5.2

VIM編輯器:VIM - Vi IMproved 7.0

系統的環境:Linux安裝在VMWare 7.0 虛擬機上

==================================================================================

編譯Bug與Debug小結

1、makefile: 4:   *** 遺漏分隔符 。 停止 。 錯誤提示,如下圖

分析與處理: 以上錯誤提示,說明makefile文件第4行,分隔符格式不正確,導致錯誤。錯誤詳見下圖

從上圖可見,第4行爲command命令行,應該如上述方法2中強調所說,命令行應當Tab分隔縮進 ,解決後如下圖所示:

2、make: main 是最新的。 錯誤提示,如下圖

這是因爲該文件目錄中,已經存在了目標可執行文件 main ,請見下圖

解決辦法:輸入 rm main 或者 rm -f main 命令,先刪去 main 文件,然後再輸入 make 命令,進行編譯鏈接即可

編譯鏈接成功後,直接利用 ./main 運行生成的目標可執行文件即可啦 ^_^

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章