Makefile入門【1】


對於驅動開發來說,掌握 Makefile書寫是至關重要的,也許公司項目有人會專門負責Makefile書寫,但你至少可以看得懂,編譯出錯時可以結合 Makefile 對問題進行快速定位,更何況萬一哪天PL讓你去寫 Makefile ....所以,學習吧~


參考教程:

[1] C語言編譯過程解析 && 動態(.so)\靜態(.a)鏈接區別

[2] Makefile經典教程


1、Makefile概述

  • makefile 關係到了整個工程的編譯規則。
  • 一個工程中的源文件 不計數,其按類型、功能、模塊分別放在若干個目錄中,makefile 定 義了一系列的規則來指定,哪些文件需要先編譯,哪些文件需要後編 譯,哪些文件需要重新編譯,甚至於進行更復雜的功能操作,因爲 makefile 就像一個 Shell 腳本一樣,其中也可以執行操作系統的命令。 makefile 帶來的好處就是——“自動化編譯”,一旦寫好,只需要一個 make 命令,整個工程完全自動編譯,極大的提高了軟件開發的效率。
  • make 是一個命令工具,是一個解釋 makefile 中指令的命令工具,一 般來說,大多數的 IDE 都有這個命令,比如:Delphi 的 make,Visual C++的 nmake,Linux 下 GNU 的 make。可見,makefile 都成爲了一 種在工程方面的編譯方法。

2、編譯和鏈接

    關於程序編譯的一些規範和方法,一般來說,無論是 C、C++、還是 pas,首先要把源文件編譯成中間代碼文件,在 Windows 下也就是 .obj 文件,UNIX 下是 .o 文件,即 Object File,這個動作 叫做編譯(compile)(嚴格來說,這裏的編譯指的是C中的預處理+編譯+彙編 -------> .o文件)。然後再把大量的 Object File 合成執行文件,這 個動作叫作鏈接(link)。

  • 編譯時,編譯器需要的是語法的正確函數與變量的聲明的正確。對 於後者,通常是你需要告訴編譯器頭文件的所在位置(頭文件中應該只是聲明,而定義應該放在 C/C++文件中,,如果函數未被聲明,編譯器會給出一個警告,但可以生成 Object File。),只要所有的語法正確, 編譯器就可以編譯出中間目標文件。一般來說,每個源文件都應該對 應於一箇中間目標文件(O 文件或是 OBJ 文件)。
  • 鏈接時,主要是鏈接函數和全局變量,所以,我們可以使用這些中間目標文件(O 文件或是 OBJ 文件)來鏈接我們的應用程序。鏈接器並不管函數所在的源文件,只管函數的中間目標文件(Object File), 在大多數時候,由於源文件太多,編譯生成的中間目標文件太多,而在鏈接時需要明顯地指出中間目標文件名,這對於編譯很不方便,所以,我們要給中間目標文件打個包,在 Windows 下這種包叫“庫文件” (Library File),也就是 .lib 文件,在 UNIX 下,是 Archive File,也 就是 .a 文件。

3、Makefile規則

target ... : prerequisites ...
    command
...
...

  • target 也就是一個目標文件,可以是 Object File,也可以是執行文件。還可以是一個標籤(Label),對於標籤這種特性,在後續的“僞目標”章節中會有敘述。
  • prerequisites 就是,要生成那個 target 所需要的文件或是目標。
  • command 也就是 make 需要執行的命令。(任意的 Shell 命令)

這是一個文件的依賴關係,也就是說,target 這一個或多個的目標文件依賴於 prerequisites 中的文件,其生成規則定義在 command 中。說 白一點就是說,prerequisites 中如果有一個以上的文件比 target 文件要新的話,command 所定義的命令就會被執行。這就是 Makefile 的規 則。也就是 Makefile 中最核心的內容。

在定義好依賴關係後,後續的那一行定義瞭如何生成目標文件的操作系統命令,一定要以一個Tab 鍵作爲開頭。記住,make並不管命令是怎麼工作的,他只管執行所定義的命令。make會比較 targets 文件和 prerequisites 文件的修改日期,如果 prerequisites 文件的日期要比 targets 文件的日期要新,或者 target 不存在的話,那麼,make 就會執 行後續定義的命令。

4、Makefile是如何工作的?

在默認的方式下,也就是我們只輸入 make 命令。那麼,

  1. make 會在當前目錄下找名字叫“Makefile”或“makefile”的 文件。
  2. 如果找到,它會找文件中的第一個目標文件(target),在 上面的例子中,他會找到“edit”這個文件,並把這個文件作爲最終的 目標文件。
  3. 如果 edit 文件不存在,或是 edit 所依賴的後面的 .o 文件的文件修改時間要比 edit 這個文件新,那麼,他就會執行後面所定義的命令來生成 edit 這個文件。
  4. 如果 edit 所依賴的.o 文件也存在,那麼 make 會在當前文件中找目標爲.o 文件的依賴性,如果找到則再根據那一個規則生成.o 文件。(這有點像一個堆棧的過程)
  5. 當然,你的 C 文件和 H 文件是存在的啦,於是 make 會 生成 .o 文件,然後再用 .o 文件聲明 make 的終極任務,也就是執行文件 edit 了。

這就是整個 make 的依賴性,make 會一層又一層地去找文件的依賴關 系,直到最終編譯出第一個目標文件。在找尋的過程中,如果出現錯 誤,比如最後被依賴的文件找不到,那麼 make 就會直接退出,並報 錯,而對於所定義的命令的錯誤,或是編譯不成功,make 根本不理。 make 只管文件的依賴性,即,如果在我找了依賴關係之後,冒號後 面的文件還是不在,那麼對不起,我就不工作啦。

5、Makefile中的變量

edit : main.o kbd.o command.o display.o insert.o search.o files.o utils.o
    cc -o edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o

我們可以看到[.o]文件的字符串被重複了兩次,如果我們的工程需要 加入一個新的[.o]文件,那麼我們需要在兩個地方加(應該是三個地 方,還有一個地方在 clean 中)。當然,我們的 makefile 並不複雜,所 以在兩個地方加也不累,但如果 makefile 變得複雜,那麼我們就有可能會忘掉一個需要加入的地方,而導致編譯失敗。所以,爲了 makefile 的易維護,在 makefile 中我們可以使用變量。makefile 的變量也就是一個字符串,理解成 C 語言中的宏可能會更好。

比如,我們聲明一個變量,OBJ。我們在 makefile 一開始就這樣定義: OBJ = main.o kbd.o command.o display.o insert.o search.o files.o utils.o 於是,我們就可以很方便地在我們的 makefile 中以 $(objects) 的方式 來使用這個變量了,於是我們的改良版 makefile 就變成下面這個樣子:

objects = main.o kbd.o command.o display.o insert.o search.o files.o utils.o
edit : $(objects)
    cc -o edit $(objects)

6、讓Makefile自己推導

GNU的make很強大,它可以自動推導文件以及文件依賴關係後面的命令,於是我們就沒必要去在每一個[.o]文件後都寫上類似的命令, 因爲,我們的 make 會自動識別,並自己推導命令。

只要 make 看到一個 whatever.o 文件,它就會自動的把 whatever.c 文件加在依賴關係中,並且 cc -c whatever.c 也會被推導出來,

objects = main.o kbd.o command.o display.o insert.o search.o files.o utils.o
edit : $(objects)
    cc -o edit $(objects)
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h
.PHONY : clean
clean :
    rm edit $(objects)

這種方法,也就是 make 的“隱晦規則”。上面文件內容中,“.PHONY” 表示,clean 是個僞目標文件。

7、另類風格的 makefile

即然我們的 make 可以自動推導命令,那麼我看到那堆[.o]和[.h]的依 賴就有點不爽,那麼多的重複的[.h],能不能把其收攏起來,好吧, 沒有問題,這個對於 make 來說很容易,誰叫它提供了自動推導命令和文件的功能呢?來看看最新風格的 makefile 吧。

objects = main.o kbd.o command.o display.o insert.o search.o files.o utils.o
edit : $(objects)
    cc -o edit $(objects)
$(objects) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h
.PHONY : clean
clean :
    rm edit $(objects)

這種風格,讓我們的 makefile 變得很簡單,但我們的文件依賴關係就顯得有點凌亂了。魚和熊掌不可兼得。還看你的喜好了。我是不喜歡 這種風格的,一是文件的依賴關係看不清楚,二是如果文件一多,要加入幾個新的.o 文件,那就理不清楚了

8、清空目標文件的規則

每個 Makefile 中都應該寫一個清空目標文件(.o 和執行文件)的規則, 這不僅便於重編譯,也很利於保持文件的清潔。

.PHONY : clean
clean :
    -rm edit $(objects)

.PHONY 意思表示 clean 是一個“僞目標”,。而在 rm 命令 前面加了一個小減號的意思就是,也許某些文件出現問題,但不要管, 繼續做後面的事當然,clean 的規則不要放在文件的開頭,不然, 這就會變成 make 的默認目標,相信誰也不願意這樣。不成文的規矩 是——“clean 從來都是放在文件的最後”。


 

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