1. 程式人生 > >深入學習Make命令和Makefile(上)

深入學習Make命令和Makefile(上)

make是Linux下的一款程式自動維護工具,配合makefile的使用,就能夠根據程式中模組的修改情況,自動判斷應該對那些模組重新編譯,從而保證軟體是由最新的模組構成。本文分為上下兩部分,我們將緊緊圍繞make在軟體開發中的應用展開詳細的介紹。

一、都是原始檔太多惹得禍

當我們在開發的程式中涉及眾多原始檔時,常常會引起一些問題。首先,如果程式只有兩三個原始檔,那麼修改程式碼後直接重新編譯全部原始檔就行了,但是如果程式的原始檔較多,這種簡單的處理方式就有問題了。

設想一下,如果我們只修改了一個原始檔,卻要重新編譯所有原始檔,那麼這顯然是在浪費時間。其次,要是隻重新編譯那些受影響的檔案的話,我們又該如何確定這些檔案呢?比如我們使用了多個頭檔案,那麼它們會被包含在各個原始檔中,修改了某些標頭檔案後,那些原始檔受影響,哪些與此無關呢?如果採取拉網式大檢查的話,可就費勁了。

由此可以看出,原始檔多了可真是件讓人頭疼的事。幸運的是,實用程式make可以幫我們解決這兩個問題——當程式的原始檔改變後,它能保證所有受影響的檔案都將重新編譯,而不受影響的檔案則不予編譯,這真是太好了。

二、Make程式的命令列選項和引數

我們知道,make程式能夠根據程式中各模組的修改情況,自動判斷應對哪些模組重新編譯,保證軟體是由最新的模組構建的。至於檢查哪些模組,以及如何構建軟體由makefile檔案來決定。

雖然make可以在makefile中進行配置,除此之外我們還可以利用make程式的命令列選項對它進行即時配置。Make命令引數的典型序列如下所示:

make [-f makefile檔名][選項][巨集定義][目標]

這裡用[]括起來的表示是可選的。命令列選項由破折號“–”指明,後面跟選項,如

make –e

如果需要多個選項,可以只使用一個破折號,如

make –kr

也可以每個選項使用一個破折號,如

make –k –r

甚至混合使用也行,如

make –e –kr

Make命令本身的命令列選項較多,這裡只介紹在開發程式時最為常用的三個,它們是:

–k:
如果使用該選項,即使make程式遇到錯誤也會繼續向下執行;如果沒有該選項,在遇到第一個錯誤時make程式馬上就會停止,那麼後面的錯誤情況就不得而知了。我們可以利用這個選項來查出所有有編譯問題的原始檔。

–n:
該選項使make程式進入非執行模式,也就是說將原來應該執行的命令輸出,而不是執行。

–f :
指定作為makefile的檔案的名稱。 如果不用該選項,那麼make程式首先在當前目錄查詢名為makefile的檔案,如果沒有找到,它就會轉而查詢名為Makefile的檔案。如果您在Linux下使用GNU Make的話,它會首先查詢GNUmakefile,之後再搜尋makefile和Makefile。按照慣例,許多Linux程式設計師使用Makefile,因為這樣能使Makefile出現在目錄中所有以小寫字母命名的檔案的前面。所以,最好不要使用GNUmakefile這一名稱,因為它只適用於make程式的GNU版本。

當我們想構建指定目標的時候,比如要生成某個可執行檔案,那麼就可以在make命令列中給出該目標的名稱;如果命令列中沒有給出目標的話,make命令會設法構建makefile中的第一個目標。我們可以利用這一特點,將all作為makefile中的第一個目標,然後將讓目標作為all所依賴的目標,這樣,當命令列中沒有給出目標時,也能確保它會被構建。

三、Makefile概述

上面提到,make命令對於構建具有多個原始檔的程式有很大的幫助。事實上,只有make命令還是不夠的,前面說過還必用須makefile告訴它要做什麼以及怎麼做才行,對於程式開發而言,就是告訴make命令應用程式的組織情況。

我們現在對makefile的位置和數量簡單說一下。一般情況下,makefile會跟專案的原始檔放在同一個目錄中。另外,系統中可以有多個makefile,一般說來一個專案使用一個makefile就可以了;如果專案很大的話,我們就可以考慮將它分成較小的部分,然後用不同的makefile來管理專案的不同部分。

make命令和Makefile配合使用,能給我們的專案管理帶來極大的便利,除了用於管理原始碼的編譯之外,還用於建立手冊頁,同時還能將應用程式安裝到指定的目錄。

因為Makefile用於描述系統中模組之間的相互依賴關係,以及產生目標檔案所要執行的命令,所以,一個makefile由依賴關係和規則兩部分內容組成。下面分別加以解釋。

依賴關係由一個目標和一組該目標所依賴的原始檔組成。這裡所說的目標就是將要建立或更新的檔案,最常見的是可執行檔案。規則用來說明怎樣使用所依賴得檔案來建立目標檔案。

當make命令執行時,會讀取makefile來確定要建立的目標檔案或其他檔案,然後對原始檔的日期和時間進行比較,從而決定使用那些規則來建立目標檔案。一般情況下,在建立起最終的目標檔案之前,肯定免不了要建立一些中間性質的目標檔案。這時,Make命令也是使用makefile來確定這些目標檔案的建立順序,以及用於它們的規則序列。

四、makefile中的依賴關係

make程式自動生成和維護通常是可執行模組或應用程式的目標,目標的狀態取決於它所依賴的那些模組的狀態。Make的思想是為每一塊模組都設定一個時間標記,然後根據時間標記和依賴關係來決定哪一些檔案需要更新。一旦依賴模組的狀態改變了,make就會根據時間標記的新舊執行預先定義的一組命令來生成新的目標。

依賴關係規定了最終得到的應用程式跟生成它的各個原始檔之間的關係。如下面的圖1描述了可執行檔案main對所有的源程式檔案及其編譯產生的目標檔案之間的依賴關係,見下圖:

 

圖1  模組間的依賴關係

就圖1而言,我們可以說可執行程式main依賴於main.o、f1.o和ff1.o。與此同時,main.o依賴於main.c和def1.h;f1.o依賴於f1.c、def1.h和def2.h;而ff1.o則依賴於ff1.c、def2.h和def3. h。在makefile中,我們可以用目標名稱,加冒號,後跟空格鍵或tab鍵,再加上由空格鍵或tab鍵分隔的一組用於生產目標模組的檔案來描述模組之間的依賴關係。對於上例來說,可以作以下描述:

main: main.o f1.o f2.o
main.o: main.c def1.h
f1.o: f1.c def1.h def2.h
f2.o: f2.c def2.h def3.h

不難發現,上面的各個原始檔跟各模組之間的關係具有一個明顯的層次結構,如果def2.h發生了變化,那麼就需要更新f1.o和f2.o,而f1.o和f2.o發生了變化的話,那麼main也需要隨之重新構建。

預設時,make程式只更新makefile中的第一個目標,如果希望更新多個目標檔案的話,可以使用一個特殊的目標all,假如我們想在一個makefile中更新main和hello這兩個程式檔案的話,可以加入下列語句達到這個目的:

all: main hello

五、makefile中的規則

除了指明目標和模組之間的依賴關係之外,makefile還要規定相應的規則來描述如何生成目標,或者說使用哪些命令來根據依賴模組產生目標。就上例而言,當make程式發現需要重新構建f1.o的時候,該使用哪些命令來完成呢?很遺憾,到目前為止,雖然make知道哪些檔案需要更新,但是卻不知道如何進行更新,因為我們還沒有告訴它相應的命令。

當然,我們可以使用命令gcc -c f1.c來完成,不過如果我們需要規定一個include目錄,或者為將來的除錯準備符號資訊的話,該怎麼辦呢?所有這些,都需要在makefile中用相應規則顯式地指出。

實際上,makefile是以相關行為基本單位的,相關行用來描述目標、模組及規則(即命令列)三者之間的關係。一個相關行格式通常為:冒號左邊是目標(模組)名;冒號右邊是目標所依賴的模組名;緊跟著的規則(即命令列)是由依賴模組產生目標所使用的命令。相關行的格式為:

目標:[依賴模組][;命令]

習慣上寫成多行形式,如下所示:

目標:[依賴模組]

命令

命令

需要注意的是,如果相關行寫成一行,“命令”之前用分號“;”隔開,如果分成多行書寫的話,後續的行務必以tab字元為先導。對於makefile而言,空格字元和tab字元是不同的。所有規則所在的行必須以tab鍵開頭,而不是空格鍵。初學者一定對此保持警惕,因為這是新手最容易疏忽的地方,因為幾個空格鍵跟一個tab鍵在肉眼是看不出區別的,但make命令卻能明察秋毫。

此外,如果在makefile檔案中的行尾加上空格鍵的話,也會導致make命令執行失敗。所以,大家一定要小心了,免得耽誤許多時間。

六、Makefile檔案舉例

根據圖1的依賴關係,這裡給出了一個完整的makefile檔案,這個例子很簡單,由四個相關行組成,我們將其命名為mymakefile1。檔案內容如下所示:

main: main.o f1.o f2.o
gcc -o main main.o f1.o f2.o
main.o: main.c def1.h
gcc -c main.c
f1.o: f1.c def1.h def2.h
gcc -c f1.c
f2.o: f2.c def2.h def3.h
gcc -c f2.c

注意,由於我們這裡沒有使用預設名makefile 或者Makefile ,所以一定要在make命令列中加上-f選項。如果在沒有任何原始碼的目錄下執行命令“make -f Mymakefile1”的話,將收到下面的訊息:

make: *** No rule to make target ‘main.c’, needed by ‘main.o’. Stop.

Make命令將makefile中的第一個目標即main作為要構建的檔案,所以它會尋找構建該檔案所需要的其他模組,並判斷出必須使用一個稱為main.c的檔案。因為迄今尚未建立該檔案,而makefile又不知道如何建立它,所以只好報告錯誤。好了,現在建立這個原始檔,為簡單起見,我們讓標頭檔案為空,建立標頭檔案的具體命令如下:

$ touch def1.h
$ touch def2.h
$ touch def3.h

我們將main函式放在main.c檔案中,讓它呼叫function2和function3,但將這兩個函式的定義放在另外兩個原始檔中。由於這些原始檔含有#include命令,所以它們肯定依賴於所包含的標頭檔案。如下所示:

/* main.c */
#include 
#include “def1.h”
extern void function2();
extern void function3();
int main()
{
function2();
function3();
exit (EXIT_SUCCESS);
}
/* f1.c */
#include “def1.h”
#include “def2.h”
void function2() {
}
/* f2.c */
#include “def2.h”
#include “def3.h”
void function3()

建好原始碼後,再次執行make程式,看看情況如何:

$ make -f Mymakefile1
gcc -c main.c
gcc -c f1.c
gcc -c f2.c
gcc -o main main.o f1.o f2.o
$

好了,這次順利通過了。這說明Make命令已經正確處理了makefile描述的依賴關係,並確定出了需要建立哪些檔案,以及它們的建立順序。雖然我們在makefile 中首先列出的是如何建立main,但是make還是能夠正確的判斷出這些檔案的處理順序,並按相應的順序呼叫規則部分規定的相應命令來建立這些檔案。當這些命令執行時,make程式會按照執行情況來顯示這些命令。

如今,我們對def2.h加以變動,來看看makefile能否對此作出相應的迴應:

$ touch def2.h

$ make -f Mymakefile1
gcc -c f1.c
gcc -c f2.c
gcc -o main main.o f1.o f2.o
$

這說明,當Make命令讀取makefile 後,只對受def2.h的變化的影響的模組進行了必要的更新,注意它的更新順序,它先編譯了C程式,最後連線生產了可執行檔案。現在,讓我們來看看刪除目標檔案後會發生什麼情況,先執行刪除,命令如下:

$ rm f1.o

然後執行make命令,如下所示:

$ make -f Mymakefile1
gcc -c f1.c
gcc -o main main.o f1.o f2.o
$

很好,make的行為讓我們非常滿意。

七、makefile中的巨集

在makefile中可以使用諸如XLIB、UIL等類似於Shell變數的識別符號,這些識別符號在makefile中稱為“巨集”,它可以代表一些檔名或選項。巨集的作用類似於C語言中的define,利用它們來代表某些多處使用而又可能發生變化的內容,可以節省重複修改的工作,還可以避免遺漏。

Make的巨集分為兩類,一類是使用者自己定義的巨集,一類是系統內部定義的巨集。使用者定義的巨集必須在makefile或命令列中明確定義,系統定義的巨集不由使用者定義。我們首先介紹第一種巨集。

這裡是一個包含巨集的makefile檔案,我們將其命名為mymakefile2,如下所示:

all: main
# 使用的編譯器
CC = gcc
#包含檔案所在目錄
INCLUDE = .
# 在開發過程中使用的選項
CFLAGS = -g -Wall –ansi
# 在發行時使用的選項
# CFLAGS = -O -Wall –ansi
main: main.o f1.o f2.o
$(CC) -o main main.o f1.o f2.o
main.o: main.c def1.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c main.c
f1.o: f1.c def1.h def2.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c f1.c
f2.o: f2.c def2.h def3.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c f2.c

我們看到,在這裡有一些註釋。在makefile中,註釋以#為開頭,至行尾結束。註釋不僅可以幫助別人理解我們的makefile,如果時間久了,有些東西我們自己也會忘掉,它們對makefile的編寫者來說也是很有必要的。

現在言歸正傳,先看一下巨集的定義。我們既可以在make命令列中定義巨集,也可以在makefile中定義巨集。在makefile中定義巨集的基本語法是:

巨集識別符號=值列表

其中,巨集識別符號即巨集的名稱通常全部大寫,但它實際上可以由大、小寫字母、阿拉伯數字和下劃線構成。等號左右的空白符沒有嚴格要求,因為它們最終將被make刪除。至於值列表,既可以是零項,也可以是一項或者多項。如:

LIST_VALUE = one two three

當一個巨集定義之後,我們就可以通過$(巨集識別符號)或者${巨集識別符號}來訪問這個識別符號所代表的值了。

在makefile中,巨集經常用作編譯器的選項。很多時候,處於開發階段的應用程式在編譯時是不用優化的,但是卻需要除錯資訊;而正式版本的應用程式卻正好相反,沒有除錯資訊的程式碼不僅所佔記憶體較小,進過優化的程式碼執行起來也更快。

對於Mymakefile1來說,它假定所用的編譯器是gcc,不過在其他的UNIX系統上,更常用的編譯器是cc或者c89,而非gcc。如果你想讓自己的makefile適用於不同的UNIX作業系統,或者在一個系統上使用其他種類的編譯器,這時就不得不對這個makefile中的多處進行修改。

但對於mymakefile2來說則不存在這個問題,我們只需修改一處,即巨集定義的值就行了。除了在makefile中定義巨集的值之外,我們還可以在make命令列中加以定義,如:

$ make CC=c89

當命令列中的巨集定義跟makefile中的定義有衝突時,以命令列中的定義為準。當在makefile檔案之外使用時,巨集定義必須作為單個引數進行傳遞,所以要避免使用空格,但是更妥當的方法是使用引號,如:

$ make “CC =   c89”

這樣就不必擔心空格所引起的問題了。現在讓我們將前面的編譯結果刪掉,來測試一下mymakefile2的工作情況。命令如下所示:

$ rm *.o main

$ make -f Mymakefile2
gcc -I. -g -Wall -ansi -c main.c
gcc -I. -g -Wall -ansi -c f1.c
gcc -I. -g -Wall -ansi -c f2.c
gcc -o main main.o f1.o f2.o
$

就像我們看到的那樣,Make程式會用相應的定義來替換巨集引用$(CC )、$(CFLAGS )和$(INCLUDE),這跟C語言中的巨集的用法比較相似。

上面介紹了使用者定義的巨集,現在介紹make的內部巨集。常用的內部巨集有:

$? :比目標的修改時間更晚的那些依賴模組表。
[email protected] :當前目標的全路徑名。可用於使用者定義的目標名的相關行中。
$< :比給定的目標檔案時間標記更新的依賴檔名。
$* :去掉字尾的當前目標名。例如,若當前目標是pro.o,則$*表示pro。



相關推薦

深入學習Make命令Makefile

make是Linux下的一款程式自動維護工具,配合makefile的使用,就能夠根據程式中模組的修改情況,自動判斷應該對那些模組重新編譯,從而保證軟體是由最新的模組構成。本文分為上下兩部分,我們將緊緊圍繞make在軟體開發中的應用展開詳細的介紹。 一、都是原始檔太多惹得禍

深入學習Make命令Makefile1

        make是Linux下的一款程式自動維護工具,配合makefile的使用,就能夠根據程式中模組的修改情況,自動判斷應該對那些模組重新編譯,從而保證軟體是由最新的模組構成。本文分為上下兩部分,我們將緊緊圍繞make在軟體開發中的應用展開詳細的介紹。

深入學習Make命令Makefile(4)

 七、makefile中的巨集 在makefile中可以使用諸如XLIB、UIL等類似於Shell變數的識別符號,這些識別符號在makefile中稱為“巨集”,它可以代表一些檔名或選項。巨集的作用類似於C語言中的define,利用它們來代表某些多處使用而又可能發生變化的

深入學習前端MVCMVVM

MVC是一種開發模式,就是一種模型—檢視—控制器(MVC)模式。 在php專案中,nodejs專案中,很容易實現MVC。比如一個nodeJS的MVC: 先說後臺的MVC 一、model層 模型層:模型中的邏輯嚴重依賴永續性。 這裡用的是mongoose

深入學習前端MVCMVVM

上一節說了後臺的MVC,現在開始講重點,前端的MVC又是一個什麼鬼。 很長一段時間我都沒有搞清楚MVC和MVVM。 一直在說ng是MVC,react和Vue是MVVM,MVVM我用過了,用過vue和react,他們的資料繫結,那麼MVC究竟是什麼樣子呢?

Servlet深入學習,規範,理解實現

學習參考資料: (1)Servet 3.1 final 規範; (2)《Java Web高階程式設計》; (3)《深入分析Java Web技術內幕》(第2版); 心得:雖然現在是實際工作中很少直接使用Servlet,但瞭解Servlet規範中對不同元件(

make命令makefile

空格 這一 file mman 情況 相關 描述 end targe make命令和Makefiles: 1. make是一個命令,解釋makefile中指令的命令工具,不同的IDE有自己的make命令。 1. make命令不知道怎麽去構建程序,必須有一個文件告訴make命

簡介make命令makefile文件

tab linux 後綴 依賴關系 函數調用 創建方式 href oca printf 一、為什麽要用到 make 命令和 makefile 文件   在 Linux 下編寫一個程序,每次編譯都需要在命令行一行一行的敲命令。如果是一個很小的程序還好說,命令不怎的復雜,編譯速

簡介make命令makefile檔案

一、為什麼要用到 make 命令和 makefile 檔案   在 Linux 下編寫一個程式,每次編譯都需要在命令列一行一行的敲命令。如果是一個很小的程式還好說,命令不怎的複雜,編譯速度也挺快,但是對於大型程式來說,這樣無疑很麻煩,且不說可能會敲錯命令,有時候僅僅改動了一個小地方,卻需要將整個程式全部重新

Pro Android學習筆記一五五 感測器5 磁場感測器方位

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Kotlin學習---函式的定義呼叫

1.1 處理集合:可變引數、中綴呼叫和庫的支援 本章節中會展示Kotlin標準庫中用來處理集合的一些方法。另外還包括幾個相關的語法特性: - vararg 可變引數,用來宣告一個函式將可能有任意數量的引數。 - 中綴表示法,當你呼叫一些 只有一個

《機器學習實戰》學習筆記之提升Adaboost基礎理論以及演算法推導

轉載請註明作者和出處:http://blog.csdn.net/john_bh/ CSDN部落格專欄:## Github程式碼獲取:## 執行平臺: Windows Python版本: Python3.6 IDE: Sublime text3

Java for Web學習筆記九十:訊息叢集5利用websocket實現訂閱釋出

叢集中的訂閱和釋出 利用spring framework在本app內的訂閱和釋出十分簡單。當我們系統越來越複雜的時候,我們需要向其他app釋出訊息。本學習將給出一個通過websocket來實現不同app之間訊息的訂購和釋出。 在小例子中,我們在所有節點之間都建立webSoc

學習Linux基本命令60個

一、安裝與登陸 1.login 登陸系統 許可權:所有使用者 login [name] [-p] [-h 主機](環境引數  使用者名稱) 2.shutdown 關閉計算機 超級使用者 shutdown [-h] [-i] [-k] [-m] [-t](關閉電源 顯示系統

JAVA學習筆記5物件

1.        面向物件程式設計(OOP)就是使用物件進行程式設計。物件有自己的特性,狀態和行為。物件的狀態是由具有當前值得資料域(又稱屬性)的集合構成。物件的行為是方法的集合定義的。呼叫物件的一個方法就是要求物件執行一次任務。 2.        類是定義同一型別

【HEVC學習與研究】39、HEVC幀內編碼的原理實現

【前面N篇博文都講了一些HEVC幀內預測的程式碼結構和簡單的方法,但是尚未對整體的演算法和實現做一個比較完整的描述。本篇藉助參考文獻《High Efficiency Video Coding (HEVC) -- Algorithms and Architectures》的

UVM序列篇之二:sequenceitem

技術 一點 目標 idt 需要 開始 掛載 ron 前行 無論是自駕item,穿過sequencer交通站,通往終點driver,還是坐上sequence的大巴,一路沿途觀光,最終跟隨導遊停靠到風景點driver,在介紹如何駕駛item和sequence,遵守什麽交規,最終

多線程編程學習筆記——asyncawait

處理 sync ext 操作 line cnblogs 編程 技術 容器 接上文 多線程編程學習筆記——async和await(一) 三、 對連續的異步任務使用await操作符 本示例學習如何閱讀有多個await方法方法時,程序

多線程編程學習筆記——asyncawait

result ask aps nta cti ise 線程編程 學習筆記 top 接上文 多線程編程學習筆記——async和await(一) 接上文 多線程編程學習筆記——async和await(二) 五、 處理異步操

linux文件壓縮打包

fff nag mark com -o mar col color .com 6.1壓縮打包介紹6.2gzip壓縮工具6.3bzip2壓縮工具6.4xz壓縮工具6.1壓縮打包介紹6.2gzip壓縮工具6.3bzip2壓縮工具6.4xz壓縮工具linux文件壓縮和打包(上)