換個角度說makefile
作為Linux下的C/C++開發者,沒接觸過makefile一定說不過去,通常構建大型的C/C++專案都離不開makefile,也許你使用的是cmake或者其他類似的工具,但它們的本質都是類似的。
作為一個輕度使用者,應讀者要求,斗膽介紹一下makefile,不過與普通的makfile教程不同的是,本文準備從另外一個角度來介紹。如有不妥之處,歡迎指出。
makefie到底是什麼
在Linux下,對於下面這個簡單的程式
//來源:公眾號【程式設計珠璣】
//main。c
#include
#include
int
main
()
{
int
a
=
10
;
int
b
=
4
;
int
c
=
pow
(
a
,
b
);
printf
(
“10^4 = %d”
,
c
);
return
0
;
}
我們通常使用gcc就可以編譯得到想要的程式了:
$ gcc -o main main。c -lm
(如果不理解為什麼要加-lm,請參考《一個奇怪的連結問題》)。
對於單個檔案的簡單程式,一條命令就可以直接搞定了(編譯+連線),但是如果是一個複雜的工程,可能有成千上萬個檔案,然後需要連結大量的動態或靜態庫。試想一下,你還想一條一條命令執行嗎?
懶惰的基因是刻在程式設計師骨子裡的。
因此你可能會想,那我寫個指令碼好了。嗯,聽起來好多了。
檔案多就多,你告訴我要編譯哪裡的檔案,我遍歷一下就好了,你再告訴我要連結哪些庫,我一一幫你連結上就好了。
然而到這裡又會想,既然編譯連結都是這麼類似的過程,能不能給它們寫一些通用的規則,搞得這麼複雜幹嘛?然後按照規則去執行就好了。
而makefile就是這樣的一個規則檔案,make是規則的解釋執行者。可以類比shell指令碼和bash解釋程式的關係。
所以,makefile並不僅僅用於編譯連結,只不過它非常適合用於編譯連結。
makefile什麼樣?
它最重要的規則語法如下:
[tab]
咋一看,就這麼個玩意?但是什麼意思?
target 要生成的目標檔名稱
要依賴的檔案
[tab] 對,就是tab鍵,初學者很容易忽略這個問題,請用tab
要執行的指令
關鍵內容就這些,但是要細講會有很多內容,本文僅舉個簡單的例子。假設要將前面的main。c複製名為pow。c的檔案。
那麼我們可以得到:
target: pow。c 目標名稱
prerequisites:main。c,即得到pow。c需要有main。c
commands:cp main。c pow。c
因此我們得到我們的makefile檔案內容如下:
pow。c:main。c
cp main。c pow。c
clean:
rm pow。c
假設當前目錄下沒有main。c檔案,然後在當前目錄下執行:
$ make pow。c
make: *** No rule to make target `main。c‘, needed by `pow。c’。 Stop。
我們發現會報錯,因為你要依賴的檔案找不到,而且也沒有其他規則能夠生成它。
現在把main。c放在當前目錄下後繼續執行:
$ make
cp main。c pow。c
看見沒有,執行完make命令之後,我們的pow。c檔案終於有了。
而執行下面的命令後:
$ make clean
rm pow。c
你就會發現pow。c被刪除了。
如果當前目錄有clean檔案會發生什麼?
$ make clean
make: `clean‘ is up to date。
至於原因,後面會講到。
這裡注意,如果你的makefile檔案的檔名不是makefile,那麼就需要指定具體名字,例如假設前面的檔名為test。txt:
$ make -f test。txt
以上例子介紹了makefile使用的基本流程,生成目標,清除目標。然而實際上這裡面的門道還有很多,例如偽目標,自動推導,隱晦規則,變數定義。本文作為認識性的文章暫時不具體介紹。
總結來說就是,給規則,按照規則生成目標。
makefile做了什麼?
網上有很多教程介紹如何編寫makefile的,很多也非常不錯。不過本文換個角度來說。
既然我們要學makefile,那麼就需要知道構建C/C++專案的時候,它應該做什麼?然後再去學習如何編寫makefile。
實際上它主要做的事情也很清楚,那就是編譯和連結。這個在《helo程式是如何程式設計可執行檔案的》中已經有所介紹,還不瞭解的朋友可以簡單瞭解一下。那麼放到makefile中具體要做什麼呢?
將原始碼檔案編譯成可重定位目標檔案。o(參考《靜態庫和動態庫的區別》)
設定編譯器選項,例如是否開啟最佳化,傳遞宏,開啟警告等
連結,將靜態庫或動態庫與目標檔案連結
所以問題就變成了,如何利用makefile的語法規則快速的將成千上萬的。c編譯成。o,並且正確連結到需要的庫。
而如果用makefile應該怎麼寫才能得到我們的程式呢?為了幫助說明,我們把前面的編譯命令拆分為兩條:
$ gcc -g -Wall -c main。c -o main。o
$ gcc -o main main。o -lm
設定編譯器
由於我們使用的是gcc編譯器(套件),因此可以像下面這樣寫:
CC=gcc
為了擴充套件性考慮,常常將編譯器定義為某個變數,後面使用的時候就會方便很多。
設定編譯選項
比如我們要設定-g選項用來除錯,設定-Wall選項來輸出更多警告資訊。
CFLAGS=-g -Wall
設定連結庫
我們這裡只用到了libm。so庫
LIBS=-lm
編譯
我們的目標檔案是main。o依賴main。c,該規則應該是這樣的:
OBJ=main。o
$(OBJ):main。c
$(CC) $(CFLAGS) -c main。c -o $(OBJ)
這樣就得到了我們的目標檔案。
連結
接下來就需要將目標檔案和庫檔案連結在一起了。
TARGET=main
$(target):main。o
$(CC) $(CFLAGS) -o $(TARGET) $(OBJ) $(LIBS)
而為了使用make clean,即通常用於清除這些中間檔案,因此需要加一個偽目標clean:
。PHONY:clean
clean:
rm $(OBJ) $(TARGET)
偽目標的意思是,它不是一個真正的要生成的目標檔案,。PHONY:clean說明了clean是一個偽目標。在這種情況下,即使當前目錄下有clean檔案,它也仍然會執行後面的指令。
否則如果當前目錄下有clean檔案,將不會執行rm動作,而認為目標檔案已經是最新的了。
完整內容
CC=gcc
CFLAGS=-g -Wall
LIBS=-lm
OBJ=main。o
$(OBJ):main。c
$(CC) $(CFLAGS) -c main。c -o $(OBJ)
TARGET=main
$(TARGET):main。o
$(CC) $(CFLAGS) -o $(TARGET) $(OBJ) $(LIBS)
。PHONY:clean
clean:
rm $(OBJ) $(TARGET)
可以看到,makefile檔案中有三個目標,分別是main。o,main和clean,其中clean是一個偽目標。
注意,由於第一個目標是main。o,因此你單單執行make的時候,它只是會生成main。o而已,如果你再執行一次會發現它提示你說main。o已經是最新的了:
$ make
gcc -g -Wall -c main。c -o main。o
$ make
make: `main。o’ is up to date。
為了得到main,我們執行:
$ make main
gcc -g -Wall -c main。c -o main。o
gcc -g -Wall -o main main。o -lm
$ ls
main main。c main。o makefile
當然你也可以調整目標順序。這裡的目標檔案main依賴的是main。o,它開始會去找main。o,發現這個檔案也沒有,就會看是不是有規則會生成main。o,欸,你還別說,真有。main。o又依賴main。c,也有,最終按照規則就會先生成main。o,然後生成mian。
如果要清除這些目標檔案,那麼可以執行make clean:
$ make clean
rm main。o main
$ ls
main。c makefile
總結
本文主要介紹了兩部分內容。
makefile是什麼東西
它是一個規則檔案,裡面按照某種語法寫好了,然後使用make來解釋執行,就像shell指令碼要用bash解釋執行一樣。通常會用makefile來構建C/C++專案。
構建C/C++專案的makefile做了什麼
makefile主要做下面的事情(以C程式為例)
用變數儲存各種設定項,例如編譯選項,編譯器,宏,包含的標頭檔案等
把。c編譯成。o
把。o與庫進行連結
清除生成的檔案
安裝程式
其中最關鍵的事情就是編譯連結,即想辦法把。c變成。o(可重定位目標檔案);。o+。so(動態庫)+。a(靜態庫)變成可執行檔案。
對於文字提到的例子,看起來實在有些笨拙,一條指令搞定,卻要寫這麼多行的makefile,但是它卻指出了通常編寫makefile的基本思路。
對於一個複雜的專案而言,makefile還有很多東西可介紹,例如如何設定變數,如何交叉編譯,如何多個目錄編譯,如何自動推導,如何分支選擇等等。這些都是後話了。
來源:公眾號【程式設計珠璣】
作者:守望先生
ID:shouwangxiansheng
原文地址:makefile入門
相關精彩推薦
如何製作屬於自己的靜態庫?
一文帶你瞭解靜態庫和動態庫
hello程式是如何被編譯出來的?
一個奇怪的連結問題
如何動態庫的製作與兩種使用方式你掌握了嗎?
關注公眾號【程式設計珠璣】,獲取更多Linux/C/C++/資料結構與演算法/計算機基礎/工具等原創技術文章。後臺免費獲取經典電子書和影片資源