第一章:概述 1.1:make概述 在linux環境下使用make工具能夠比較容易的構建一個屬於自己的工程,整個工程的編譯只需要一個命令就可以完成編譯、連接以至於最後的執行。不過我們需要投入一些時間去學習如何完成makefile文件的編寫,這個文件也是make正常工作的基礎。 所要完成的makefi ...
第一章:概述
1.1:make概述
在linux環境下使用make工具能夠比較容易的構建一個屬於自己的工程,整個工程的編譯只需要一個命令就可以完成編譯、連接以至於最後的執行。不過我們需要投入一些時間去學習如何完成makefile文件的編寫,這個文件也是make正常工作的基礎。
所要完成的makefile文件描述了整個工程的編譯、連接等規則。其中包括:工程中的哪些源文件需要編譯以及如何編譯、需要創建那些庫文件以及如何創建這些庫文件、如何最後產生我們想要的可執行文件。
make是一個命令工具,他解釋了makefile中的指令。在makefile文件中描述了整個工程所有文件的編譯順序、編譯規則。makefile有自己的書寫格式,關鍵字、函數。想c語言有自己的格式、關鍵字和函數一樣。而且在makefile中可以使用系統shell所提供的任何命令來完成想要的工作。
1.2:準備知識
在討論make之前首先需要明確一些基本概念:
編譯:把高級語言書寫的代碼轉換為機器可識別的機器指令。編譯後產生的機器指令雖然可被機器識別,但是還不能被執行。編譯時,編譯器檢察高級語言的語法、函數與變數的聲明是否正確。只有所有語法正確、相關變數定義正確,編譯器就可以編譯出中間目標文件。通常一個高級語言的源文件都可對應一個目標文件。目標文件在linux中預設尾碼為“.o”。
鏈接:將多個目標文件,或者目標文件和庫文件鏈接稱為可被操作系統執行的可執行程式。鏈接器不檢查函數所在的源文件,只檢查所有目標文件的定義的符號。將目標文件使用的函數和其他目標或者庫文件中的相關符號進行合併,並對所有文件中的符號進行重新安排,並連接系統相關文件最終生成可執行程式。
靜態庫:又稱文檔文件,十多個目標文件的集合。
共用庫:也是多個目標文件的集合,但是這些目標文件按照一種特殊的方式生成。模塊中各個成員的地址都是相對地址。使用此共用庫的程式在運行時,共用庫被動態載入到記憶體並和主程式在記憶體中進行鏈接。多個可執行程式可共用庫文件中的代碼段。
第二章:GUN make介紹
makemakefile告訴make以何種方式編譯和鏈接程式,當某一文件更新時,make通過比較對應文件的最後修改時間,來決定那些文件需要更新,對需要的文件執行相應命令。
2.1:makefile簡介:
當使用make工具進行編譯時,工程中一些集中文件在執行make時將會被編譯:
1、所有源文件中沒有被編譯過,則對各個c源文件進行編譯併進行鏈接,生成最後的可執行程式;
2、每一個在上次執行make之後修改過的c源代碼文件在本次執行make時將會被重新編譯;
3、頭文件在上一次執行make之後被修改。則所有包含此頭文件的c源文件在本次執行make時將會被重新編譯。
2.2:makefile規則介紹:
一個簡單的makefile描述規則組成如下:
TARGET... : PREREQUITES
COMMAND
target:規則的目標,通常是最後需要生成的文件名或者為了實現這個目標而必須的中間過程文件名。可以是目標文件、也可以是最後的可執行程式的文件名。另外目標也可以使一個make執行的動作的名稱,如clean,我們稱這樣的目標是偽目標。
prerequisites:規則的依賴。生成規則所需要的文件名列表。通常一個文件依賴於一個或者多個文件。
command:規則的命令行。是規則所要執行的動作(任意的shell命令或者是可在shell下執行的程式)。它限定了make執行這條規則時所需要的動作。
一個規則可以有多個命令行,每一條命令占一行。註意:每一個命令行必須一[tab]字元開始,[tab]字元告訴make此行是一個命令行。make按照命令完成相應的動作。這也是書寫makefile中容易產生,而且比較隱蔽的錯誤。
命令就是任意一個目標的依賴文件發生變化後重建目標的動作描述。一個目標可以沒有依賴而只有動作。
在makefile中的規則就是描述在什麼情況下、如何重建規則的目標文件,通常規則中包括了目標的依賴關係和重建目標的命令。make執行重建目標的命令,來創建或者重建目標。規則包含了文件之間的依賴關係和更新此規則目標所需要的命令。
make程式根據規則的依賴關係,決定是夠執行規則所定義的命令的過程我們稱之為執行規則。
2.3:簡單的示例
main.c:
#include"hello.h" int main(){ print(); return 0; }
hello.c:
#include"hello.h" void print(){ printf("hello world!\n"); }
hello.h:
#ifndef HELLO_H #define HELLO_H #include<stdio.h> void print(); #endif
makefile文件如下:
obj = main.o \
hello.o main: $(obj) gcc $(obj) -o main main.o: main.c hello.h gcc -c main.c -o main.o hello.o: hello.c hello.h gcc -c hello.c -o hello.o clean: rm -rf *.o main
首先書寫時,可以將一個較長行使用反斜杠分解為多行,使得更容易理解。但是需要註意:反斜杠之後不能有空格。在完成這個makefile以後;需要創建可執行程式main,所要做的就是在包含此makefile的目錄下鍵入命令make。刪除此目錄下的使用make生成的文件,也只需要鍵入命令make clean就可以了。
命令行必須以tab鍵開始,以和makefile其他行區別。就是說所有的命令行必須以tab字元開始,但並不是所有的以tab字元開始的行都作為命令行來處理。(記住:make程式本身並不關心命令是如何工作的,對目標文件的更新需要你在規則描述中提供正確的命令。make程式所做的就是當目標程式需要更新時執行規則所定義的命令)。
目標clean不是一個文件,它僅僅代表執行一個動作的標識。正常情況下,不需要執行這個規則所定義的動作,因此目標clean沒有出現在其他任何規則的依賴列表中。因此在執行make時,它所指定的動作不會被執行。除非在執行make時明確地指定它。而且目標clean沒有任何依賴文件,他只有一個目的,就是通過這個目標名來執行它所定義的命令。makefile中把那些沒有任何依賴只有執行動作的目標稱為偽目標。
2.4:make如何工作
預設情況下,make執行的是makefile中的第一個規則,此規則的第一個目標稱為最終目的或者終極目標。例如上例中的main文件。
當在shell中鍵入make命令後,make讀取當前目錄下的makefile文件,並將makefile文件中的第一個目標作為其執行的終極目標,開始處理第一個規則。在處理此規則所定義的命令之前,首先處理目標main所有的依賴文件的更新規則。這些目標文件為目標的規則的處理有下列三種情況:
1、目標文件不存在,使用其描述規則創建它;
2、目標文件存在,目標文件所以來的.c源文件或者頭文件中的任何一個比目標文件更新。則根據規則重新編譯生成它;
3、目標文件存在,目標文件比它的任何一個依賴文件更新,什麼也不做。
在makefile中的一個規則的目標不是終極目標所依賴的,那麼這個規則不會被執行,除非明確指定執行這個規則。
完成了對目標文件的創建或者更新之後,make程式將處理終極目標main所在的規則,分為以下三種情況:
1、目標文件不存在,則執行規則以創建目標main;
2、目標文件main存在,其依賴文件中有一個或者多個文件比它更新,則根據規則重新鏈接生成mian;
3、目標文件存在。它比它的任何一個依賴文件都新,則什麼都不做。
總結對一個makefile文件,make首先解析終極目標所在的規則,根據其依賴文件依次尋找創建這些以來文件的規則。首先為第一個依賴文件尋找創建規則,如果第一個依賴文件依賴於其他文件,則同樣為這個依賴文件尋找創建規則,知道為所有依賴文件找到合適的創建規則。之後make從最後一個規則回退開始執行,最終完成終極目標的第一個依賴文件的創建和更新。之後對第一個、第二個、第三個、。。。終極目標的依賴文件執行同樣的過程。最後一步是創建此規則的目標。
更新終極目標的過程中,如果任何一個規則出現錯誤make就立即報錯並退出。整個過程make只是負責執行規則,而對具體規則所描述的依賴關係的正確性、規則所定義的命令的正確性不做任何判斷。就是說,一個規則的依賴關係是否正確、描述重建目標的規則命令行是夠正確,make不做任何錯誤檢查。因此編寫一個正確的makefile文件愛你就顯得尤為重要。
2.5:制定變數
上面的例子中的obj就是一個指定變數,下麵每次使用時都可以直接使用;這樣做不但減少書寫的工作量,而且還可以減少修改而產生錯誤的可能。
2.6:自動推導規則
在使用make編譯.c源文件時,編譯.c源文件時,編譯.c源文件規則的命令可以不用明確給出。這是因為make本身存在一個預設的規則,能夠自動完成對.c文件的編譯並生成對應的目標文件。他執行命令cc -c來編譯.c文件。在makefile中我們只需要給出需要重建的目標文件名,meke會自動為這個目標文件尋找合適的依賴文件,並且使用正確的命令,來重建這個目標文件,對於上面的例子,此預設規則就是使用命令“cc -c main.c -o main.o”來創建文件'main.o'對一個目標文件。此預設規則稱為make的隱含規則。
在書寫時我們就可以省略掉描述重名的目標文件與依賴文件的規則,只需要給出那些特定的規則描述。因此上面的例子就可以更加簡單的寫成:
obj = main.o hello.o main: $(obj) cc -o main $(obj) main.o: hello.h hello.o: hello.h clean: rm -rf *.o main
2.7:另類風格的makefile
我們也可以根據依賴而不是目標對規則進行分組。上例的makefile就可以這樣來實現:
obj = main.o hello.o main : $(obj) cc -o main $(obj) #(obj) : hello.h clean: rm -rf *.o main
例子中hello.h作為所有.o文件的依賴文件。但這種書寫方式不建議,後期維護會比較麻煩。
書寫規則建議的方式是:單目標,多依賴。就是說儘量做到一個規則中只存在一個目標文件,可以有多個依賴文件。儘量避免多目標,單依賴的方式。
2.8:清除工作目錄過程文件
規則除了完成源代碼編譯之外,也可以完成其他任務,例如前面提到的清除編譯過程中產生的臨時文件的規則。
clean: rm -rf *.o main
在實際應用時,我們把這個規則寫成如下稍微複雜一些的樣子,以防止始料未及的情況。
.PHONY:clean clean: -rm -rf *.o main
這兩個實現有兩點不同:
1、通過“.PHONY”特殊目標將clean目標聲明為偽目標。避免當磁碟上存在一個名為clean文件時,目標clean所在規則的命令無法執行。
2、在命令行之前使用-,意思是忽略命令“rm”的執行錯誤。
這樣一個目標在makefile中,不能將其作為終極目標。因為我們的初衷並不是當你在命令行上輸入make以後執行刪除動作。而是要創建或者更新程式。