您當前的位置:首頁 > 歷史

換個角度說makefile

作者:由 守望 發表于 歷史時間:2020-01-04

換個角度說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入門

換個角度說makefile

相關精彩推薦

換個角度說makefile

如何製作屬於自己的靜態庫?

一文帶你瞭解靜態庫和動態庫

hello程式是如何被編譯出來的?

一個奇怪的連結問題

如何動態庫的製作與兩種使用方式你掌握了嗎?

關注公眾號【程式設計珠璣】,獲取更多Linux/C/C++/資料結構與演算法/計算機基礎/工具等原創技術文章。後臺免費獲取經典電子書和影片資源

標簽: main  makefile  檔案  pow  make