国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 學(xué)院 > 開發(fā)設(shè)計(jì) > 正文

關(guān)于makefile

2019-11-17 05:15:06
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

  0) 介紹
本文將首先介紹為什么要將你的C源代碼分離成幾個(gè)合理的獨(dú)立檔案,什么時(shí)候需要分,
怎么才能分的好。然后將會(huì)告訴你GNU Make怎樣使你的編譯和連接步驟自動(dòng)化。對(duì)于其它Make
工具的用戶來(lái)說(shuō),雖然在用其它類似工具時(shí)要做適當(dāng)?shù)恼{(diào)整,本文的內(nèi)容仍然是非常有用的。
假如對(duì)你自己的編程工具有懷疑,可以實(shí)際地試一試,但請(qǐng)先閱讀用戶手冊(cè)。

1) 多文件項(xiàng)目
1.1 為什么使用它們?
首先,多文件項(xiàng)目的好處在那里呢?
它們看起來(lái)把事情弄得復(fù)雜無(wú)比。又要header文件,又要extern聲明,而且假如需要查找
一個(gè)文件,你要在更多的文件里搜索。但其實(shí)我們有很有力的理由支持我們把一個(gè)項(xiàng)目分解成
小塊。當(dāng)你改動(dòng)一行代碼,編譯器需要全部重新編譯來(lái)生成一個(gè)新的可執(zhí)行文件。但假如你的
項(xiàng)目是分開在幾個(gè)小文件里,當(dāng)你改動(dòng)其中一個(gè)文件的時(shí)候,別的源文件的目標(biāo)文件(object
files)已經(jīng)存在,所以沒有什么原因去重新編譯它們。你所需要做的只是重現(xiàn)編譯被改動(dòng)過(guò)的
那個(gè)文件,然后重新連接所有的目標(biāo)文件罷了。在大型的項(xiàng)目中,這意味著從很長(zhǎng)的(幾分鐘
到幾小時(shí))重新編譯縮短為十幾,二十幾秒的簡(jiǎn)單調(diào)整。只要通過(guò)基本的規(guī)劃,將一個(gè)項(xiàng)目分
解成多個(gè)小文件可使你更加輕易的找到一段代碼。很簡(jiǎn)單,你根據(jù)代碼的作用把你的代碼分解
到不同的文件里。當(dāng)你要看一段代碼時(shí),你可以準(zhǔn)確的知道在那個(gè)文件中去尋找它。從很多目
標(biāo)文件生成一個(gè)程序包 (Library)比從一個(gè)單一的大目標(biāo)文件生成要好的多。當(dāng)然實(shí)際上這是
否真是一個(gè)優(yōu)勢(shì)則是由你所用的系統(tǒng)來(lái)決定的。但是當(dāng)使用 gcc/ld (一個(gè) GNU C 編譯/連接
器) 把一個(gè)程序包連接到一個(gè)程序時(shí),在連接的過(guò)程中,它會(huì)嘗試不去連接沒有使用到的部分。
但它每次只能從程序包中把一個(gè)完整的目標(biāo)文件排除在外。因此假如你參考一個(gè)程序包中某一
個(gè)目標(biāo)檔中任何一個(gè)符號(hào)的話,那么這個(gè)目標(biāo)文件整個(gè)都會(huì)被連接進(jìn)來(lái)。要是一個(gè)程序包被非
常充分的分解了的話,那么經(jīng)連接后,得到的可執(zhí)行文件會(huì)比從一個(gè)大目標(biāo)文件組成的程序包
連接得到的文件小得多。又因?yàn)槟愕某绦蚴呛苣K化的,文件之間的共享部分被減到最少,那
就有很多好處——可以很輕易的追蹤到臭蟲,這些模塊經(jīng)常是可以用在其它的項(xiàng)目里的,同時(shí)別
人也可以 更輕易的理解你的一段代碼是干什么的。當(dāng)然此外還有許多別的好處……

1.2 何時(shí)分解你的項(xiàng)目
很明顯,把任何東西都分解是不合理的。象“世界,你們好”這樣的簡(jiǎn)單程序根本就不能分,
因?yàn)閷?shí)在也沒什么可分的。把用于測(cè)試用的小程序分解也是沒什么意思的。但一般來(lái)說(shuō),當(dāng)分
解項(xiàng)目有助于布局、發(fā)展和易讀性的時(shí)候,我都會(huì)采取它。在大多數(shù)的情況下,這都是適用的。
(所謂“世界,你們好”,既 'hello world' ,只是一個(gè)介紹 一種編程語(yǔ)言時(shí)慣用的范例程序,
它會(huì)在屏幕上顯示一行 'hello world' 。是最簡(jiǎn)單的程序。)假如你需要開發(fā)一個(gè)相當(dāng)大的項(xiàng)
目,在開始前,應(yīng)該考慮一下你將如何實(shí)現(xiàn)它,并且生 成幾個(gè)文件(用適當(dāng)?shù)拿郑﹣?lái)放你的
代碼。當(dāng)然,在你的項(xiàng)目開發(fā)的過(guò)程中,你可以 建立新的文件,但假如你這么做的話,說(shuō)明你
可能改變了當(dāng)初的想法,你應(yīng)該想想是否 需要對(duì)整體結(jié)構(gòu)也進(jìn)行相應(yīng)的調(diào)整。對(duì)于中型的項(xiàng)目,
你當(dāng)然也可以采用上述技巧,但你也可以就那么開始輸入你的代碼, 當(dāng)你的碼多到難以治理的
時(shí)候再把它們分解成不同的檔案。但以我的經(jīng)驗(yàn)來(lái)說(shuō),開始時(shí) 在腦子里形成一個(gè)大概的方案,
并且盡量遵從它,或在開發(fā)過(guò)程中,隨著程序的需要而 修改,會(huì)使開發(fā)變得更加輕易。

1.3 怎樣分解項(xiàng)目
先說(shuō)明,這完全是我個(gè)人的意見,你可以(也許你真的會(huì)?)用別的方式來(lái)做。這會(huì)觸 動(dòng)
到有關(guān)編碼風(fēng)格的問(wèn)題,而大家從來(lái)就沒有停止過(guò)在這個(gè)問(wèn)題上的爭(zhēng)論。 在這里我只是給出我
自己喜歡的做法(同時(shí)也給出這么做的原因):
i) 不要用一個(gè) header 文件指向多個(gè)源碼文件(例外:程序包的header文件)。 用一個(gè)
header定義一個(gè)源碼文件的方式 會(huì)更有效,也更輕易查尋。否則改變一個(gè)源文件的結(jié)構(gòu)(并且
它的header文件)就必須重新編譯好幾個(gè)文件。
ii) 假如可以的話,完全可以用超過(guò)一個(gè)的 header 文件來(lái)指向同 一個(gè)源碼文件。有時(shí)將
不可公開調(diào)用的函數(shù)原型,類型定義 等等,從它們的C源碼文件中分離出來(lái)是非常有用的。使
用一個(gè)header文件裝公開符號(hào),用另一個(gè)裝私人符號(hào)意味著假如 你改變了這個(gè)源碼文件的內(nèi)部
結(jié)構(gòu),你可以只是重新編譯它而 不需要重新編譯那些使用它的公開 header 文件的其它的源文
件。
iii) 不要在多個(gè) header 文件中重復(fù)定義信息。 假如需要, 在其中一個(gè) header 文件里
#include 另一個(gè),但是不要重復(fù)輸入相同的 header 信息兩次。原因是假如你以后改變了這個(gè)
信息,你只需要把它改變一次,不用搜索并改變另外一個(gè)重復(fù)的信息。
iv) 在每一個(gè)源碼文件里, #include 那些聲明了源碼文件中的符 號(hào)的所有 header 文件。
這樣一來(lái),你在源碼文件和 header 文件對(duì)某些函數(shù)做出的矛盾聲明可以比較輕易的被編譯器發(fā)現(xiàn)。

1.4 對(duì)于常見錯(cuò)誤的注釋
a) 定義符 (Identifier) 在源碼文件中的矛盾:在C里,變量和函數(shù)的缺 省狀態(tài)是公用的。
因此,任何C源碼檔案都可以引用存在于其它源 碼檔中的通用(global) 函數(shù)和通用變量,既使

這個(gè)檔案沒有那個(gè)變 量或函數(shù)的聲明或原型。因此你必須保證在不同的兩個(gè)檔案里不能 用同一
個(gè)符號(hào)名稱,否則會(huì)有連接錯(cuò)誤或者在編譯時(shí)會(huì)有警告。一種避免這種錯(cuò)誤的方法是在公用的符
號(hào)前加上跟其所在源文件有 關(guān)的前綴。比如:
所有在 gfx.c 里的函數(shù)都加上前綴“gfx_”。假如你很小心的分解你的程序,使用有 意義的
函數(shù)名稱,并且不是過(guò)分 使用通用變量,當(dāng)然這根本就不是問(wèn)題。 要防止一個(gè)符號(hào)在它被定義
的源文件以外被看到,可在它的定義前 加上要害字 “static”。這對(duì)只在一個(gè)檔案內(nèi)部使用,其
它檔案都不會(huì)用到的簡(jiǎn)單函數(shù)是很有用的。
b) 多次定義的符號(hào): header 檔會(huì)被逐字的替換到你源文件里 #include 的位置的。 因此,
假如header檔被 #include 到一個(gè)以上的源文件 里,這個(gè) header 檔中所有 的定義就會(huì)出現(xiàn)在
每一個(gè)有關(guān)的源碼文件 里。這會(huì)使它們里的符號(hào)被定義一次以上, 從而出現(xiàn)連接錯(cuò)誤(見上)。
解決方法: 不要在 header 檔里定義變量。你只需要在 header 檔里聲明它們?nèi)缓笤?適當(dāng)?shù)模?br />源碼文件(應(yīng)該 #include 那個(gè) header 檔的那個(gè))里定義它們(一次)。對(duì)于初學(xué)者來(lái)說(shuō),定
義和聲明是 很輕易混淆的。聲明的作用是告訴編譯器其所聲明的符 號(hào)應(yīng)該存在,并且要有所指
定的類型。但是,它并不會(huì)使編譯器分配貯存空間。 而定 義的做用是要求編譯器分配貯存空間。
當(dāng)做一個(gè)聲明而不是做 定義的時(shí)候,在聲明前放一個(gè)要害字“extern”。 例如,我們有一個(gè)叫
“counter”的變量,假如想讓它成為公用的,我們?cè)谝粋€(gè)源碼程 序(只在一個(gè)里面)的開始定義
它:“int counter;”,再在相關(guān)的 header 檔里聲明 它:“extern int counter;”。函數(shù)原型里
隱含著 extern 的意思,所以不需顧慮這個(gè)問(wèn)題。
c) 重復(fù)定義,重復(fù)聲明,矛盾類型:
請(qǐng)考慮假如在一個(gè)C源碼文件中 #include 兩個(gè)檔 a.h 和 b.h, 而 a.h 又 #include 了 b.h
檔(原因是 b.h 檔定義了一些 a.h 需要的類型),會(huì)發(fā)生什么事呢?這時(shí)該C源碼文件
#include 了 b.h 兩次。因此每一個(gè)在 b.h 中的 #define 都發(fā)生了兩 次,每一 個(gè)聲明發(fā)生了
兩次,等等。理論上,因?yàn)樗鼈兪峭耆粯拥目截悾?所以應(yīng)該 不會(huì)有什么問(wèn)題,但在實(shí)際應(yīng)用
上,這是不符合C的語(yǔ)法 的,可能在編譯時(shí)出現(xiàn)錯(cuò)誤,或至少是警告。 解決的方法是要確定每
一個(gè) header 檔在任一個(gè)源碼文件中只被包 含了一次。我們一 般是用預(yù)處理器來(lái)達(dá)到這個(gè)目的
的。當(dāng)我們進(jìn)入 每一個(gè) header 檔時(shí),我們?yōu)檫@個(gè) header 檔 #define一個(gè)巨集 指令。只有在
這個(gè)巨集指令沒有被定義的前提下,我們 才真正使用 該 header 檔的主體。在實(shí)際應(yīng)用上,我
們只要簡(jiǎn)單的把下面一段 碼放在 每一個(gè) header 檔的開始部分:
#ifndef FILENAME_H
#define FILENAME_H
然后把下面一行碼放在最后:
#endif
用header檔的檔名(大寫的)代替上面的 FILENAME_H,用底線 代替檔名中的點(diǎn)。有些人喜歡
在 #endif 加上注釋來(lái)提醒他們這個(gè) #endif 指的是什么。例如:
#endif /* #ifndef FILENAME_H */
我個(gè)人沒有這個(gè)習(xí)慣,因?yàn)檫@其實(shí)是很明顯的。當(dāng)然這只是各人的 風(fēng)格不同,無(wú)傷大雅。
你只需要在那些有編譯錯(cuò)誤的 header 檔中加入這個(gè)技巧,但在所有的header檔中都加入也沒
什么損失,到底這是個(gè)好習(xí)慣。

1.5 重新編譯一個(gè)多文件項(xiàng)目
清楚的區(qū)別編譯和連接是很重要的。編譯器使用源碼文件來(lái)產(chǎn)生某種 形式的目標(biāo)文件
(object files)。在這個(gè)過(guò)程中,外部的符號(hào)參考并 沒有被解釋或替換。 然后我們使用連接器
來(lái)連接這些目標(biāo)文件和一些 標(biāo)準(zhǔn)的程序包再加你指定的程序包,最后連接生 成一個(gè)可執(zhí)行程序。
在這個(gè)階段,一個(gè)目標(biāo)文件中對(duì)別的文件中的符號(hào)的參考被解釋,并報(bào)告不能被解釋的參考,一
般是以錯(cuò)誤信息的形式報(bào)告出來(lái)。基本的步驟就應(yīng)該是,把你的源碼文件一個(gè)一個(gè)的編譯成目標(biāo)
文件的格 式,最后把所 有的目標(biāo)文件加上需要的程序包連接成一個(gè)可執(zhí)行文件。具體怎么做是
由你的編譯器 決定的。這里我只給出 gcc(GNU C 編譯 器)的有關(guān)命令,這些有可能對(duì)你的非
gcc 編譯器也適用。
gcc 是一個(gè)多目標(biāo)的工具。它在需要的時(shí)候呼叫其它的元件(預(yù)處理程序,編譯器,組合程
序,連接器)。具體的哪些元件被呼叫取決于 輸入文件的類型和你傳遞給它的開關(guān)。 一般來(lái)說(shuō),
假如你只給它C源碼文件,它將預(yù)處理,編譯,組合所有 的文件,然后把 所得的目標(biāo)文件連接
成一個(gè)可執(zhí)行文件(一般生成的 文件被命名為 a.out )。你當(dāng)然可以這么做,但這會(huì)破壞很多
我們 把一個(gè)項(xiàng)目分解成多個(gè)文件所得到的好處。 假如你給它一個(gè) -c 開關(guān),gcc 只把給它的文
件編譯成目標(biāo)文件, 用源碼文件的文件 名命名但把其后綴由“.c”或“.cc”變成“.o”。 假如你給
它的是一列目標(biāo)文件, gcc 會(huì)把它們連接成可執(zhí)行文件, 缺省文件名是a.out 。你可以改變?nèi)?br />省名,用開 -o 后跟你指定 的文件名。因此,當(dāng)你改變了一個(gè)源碼文件后,你需要重新編譯它:
'gcc -c filename.c' 然后重新連接你的項(xiàng)目: 'gcc -o exec_filename *.o'。 假如你改變了
一個(gè) header 檔, 你需要重新編譯所有 #include 過(guò) 這個(gè)檔的源碼文件,你可以用
'gcc -c file1.c file2.c file3.c' 然后象上邊一樣連接。 當(dāng)然這么做是很繁瑣的,幸虧我們
有些工具使這個(gè)步驟變得簡(jiǎn)單。 本文的第二部分就 是介紹其中的一件工具:GNU Make 工具。
(好家伙,現(xiàn)在才開始見真章。您學(xué)到點(diǎn)兒東西沒?)


2) GNU Make 工具
2.1 基本 makefile 結(jié)構(gòu)
GNU Make 的主要工作是讀進(jìn)一個(gè)文本文件, makefile 。這個(gè)文件里主要是有關(guān)哪些文件
(‘target’目的文件)是從哪些別的 文件(‘dependencies’依靠文件)中產(chǎn) 生的,用什么命令
來(lái)進(jìn)行 這個(gè)產(chǎn)生過(guò)程。有了這些信息, make 會(huì)檢查磁碟上的文件,假如 目的文件的時(shí)間戳
(該文件生成或被改動(dòng)時(shí)的時(shí)間)比至少它的一 個(gè)依靠文件舊的話, make 就執(zhí)行相應(yīng)的命令,
以便更新目的文件。 (目的文件不一定是最后的可執(zhí)行檔,它可以是任何一個(gè)文件。)
makefile 一般被叫做“makefile”或“Makefile”。當(dāng)然你可以 在 make 的命令行指 定別的文件
名。假如你不非凡指定,它會(huì)尋 找“makefile”或“Makefile”,因此使用這兩個(gè)名字是最簡(jiǎn)單的。
一個(gè) makefile 主要含有一系列的規(guī)則,如下:
例如,考慮以下的 makefile :
=== makefile 開始 ===
myPRog : foo.o bar.o
gcc foo.o bar.o -o myprog
foo.o : foo.c foo.h bar.h
gcc -c foo.c -o foo.o
bar.o : bar.c bar.h
gcc -c bar.c -o bar.o
=== makefile 結(jié)束 ===
這是一個(gè)非常基本的 makefile —— make 從最上面開始,把上 面第一個(gè)目的, ‘myprog’,
做為它的主要目標(biāo)(一個(gè)它需要保 證其總是最新的最終目標(biāo))。給出的 規(guī)則說(shuō)明只要文件
‘myprog’ 比文件‘foo.o’或‘bar.o’中的任何一個(gè)舊,下一行的命令將 會(huì)被執(zhí)行。但是,在
檢查文件 foo.o 和 bar.o 的時(shí)間戳之前,它會(huì)往下查找那些把 foo.o 或 bar.o 做為目標(biāo)
文件的規(guī)則。它找到的關(guān)于 foo.o 的規(guī)則,該文件的依靠文件是 foo.c, foo.h 和 bar.h 。
它從下面再找不到生成這些依靠文件的規(guī)則,它就開始檢 查磁碟 上這些依靠文件的時(shí)間戳。
假如這些文件中任何一個(gè)的時(shí)間戳比 foo.o 的新, 命令 'gcc -o foo.o foo.c' 將會(huì)執(zhí)行,
從而更新 文件 foo.o。接下來(lái)對(duì)文件 bar.o 做類似的檢查,依靠文件在這里是文件bar.c
和 bar.h 。 現(xiàn)在, make 回到‘myprog’的規(guī)則。假如剛才兩個(gè)規(guī)則中的任 何一個(gè)被執(zhí)行,
myprog 就需要重建(因?yàn)槠渲幸粋€(gè) .o 檔就會(huì)比 ‘myprog’新),因此連接命令將被 執(zhí)行。
希望到此,你可以看出使用 make 工具來(lái)建立程序的好處——前 一章中所有繁瑣的檢查
步驟都由 make 替你做了:檢查時(shí)間戳。 你的源碼文件里一個(gè)簡(jiǎn)單改變都會(huì)造成那個(gè)文件
被重新編譯(因 為 .o 文件依靠 .c 文件),進(jìn)而可執(zhí)行文件被重新連接(因?yàn)?o文件被
改變了)。其實(shí)真正的得益是在當(dāng)你改變一個(gè) header 檔的時(shí)候——你不 再需要記住那個(gè)源
碼文件依靠它,因?yàn)樗械?資料都在 makefile 里。 make 會(huì)很輕松的替你重新編譯所有
那 些因依靠這個(gè) header 文件而改變了的源碼文件,如有需 要,再 進(jìn)行重新連接。當(dāng)然,
你要確定你在 makefile 中所寫的規(guī)則是正確無(wú)誤的,只 列出那些在源碼文件 中被
#include 的 header 檔……

2.2 編寫 make 規(guī)則 (Rules)
最明顯的(也是最簡(jiǎn)單的)編寫規(guī)則的方法是一個(gè)一個(gè)的查看源碼文件,把它們的目標(biāo)
文件做為目的,而C源碼文件和被它 #include 的 header 檔做為依靠文件。但是你也要把
其它被這些 header 檔 #include 的 header 檔也列為依靠文件,還有那些被包括的文件所
包括的文件……然后你會(huì)發(fā)現(xiàn)要對(duì)越來(lái)越多的文件進(jìn)行治理,然后你的頭發(fā)開始脫落,你的脾
氣開始變壞,你的臉 色變成菜色,你走在路上開始跟電線桿子 碰撞,終于你搗毀你的電腦
顯示器,停止編程。到低有沒有些輕易點(diǎn)兒的方法呢?當(dāng)然有!向編譯器要!在編譯每一個(gè)
源碼文件的時(shí)候,它實(shí)在應(yīng) 該知道應(yīng)該包括什么樣的 header 檔。使用gcc的時(shí)候,用 -M
開關(guān),它會(huì)為每一個(gè)你給它的C文件輸出一個(gè)規(guī)則,把目標(biāo)文件 做為目的, 而這個(gè)C文件
和所有應(yīng)該被 #include 的 header 文 件將做為依靠文件。注重這個(gè)規(guī)則會(huì)加入所有header
文件,包 括被角括號(hào)(`<', `>')和雙引號(hào)(`"')所包圍的文件。其實(shí)我們可以 相當(dāng)肯定系統(tǒng)
header 檔(比如 stdio.h, stdlib.h 等等)不會(huì) 被我們更改,假如你用 -MM 來(lái)代替 -M
傳遞給 gcc, 那些用角括 號(hào)包圍的 header 檔將不會(huì)被包括。(這會(huì)節(jié)省一些編譯時(shí)間)
由 gcc 輸出的規(guī)則不會(huì)含有命令部分;你可以自己寫入你的命令 或者什么也不寫,而讓
make 使用它的隱含的規(guī)則(參考下面的 2.4 節(jié))。
2.3 Makefile 變量
上面提到 makefiles 里主要包含一些規(guī)則。它們包含的其它的東 西是變量定義。
makefile 里的變量就像一個(gè)環(huán)境變量(environment variable)。 事實(shí)上,環(huán)境變量在make
過(guò)程中被解釋成 make 的變量。這些 變量是大小寫敏感的,一般使用大寫字母。 它們可以
從幾乎任何 地方被引用,也可以被用來(lái)做很多事情,比如:
i) 貯存一個(gè)文件名列表。在上面的例子里,生成可執(zhí)行文件的 規(guī)則包含一些目標(biāo)文件
名做為依靠。在這個(gè)規(guī)則的命令行 里同樣的那些文件被輸送給 gcc 做為命令參數(shù)。假如在
這里使用一個(gè)變數(shù)來(lái)貯存所有的目標(biāo)文件名,加入新的目標(biāo) 文件會(huì)變的簡(jiǎn)單而且較不易出錯(cuò)。
ii) 貯存可執(zhí)行文件名。假如你的項(xiàng)目被用在一個(gè)非 gcc 的系 統(tǒng)里,或者假如你想使

用一個(gè)不同的編譯器,你必須將所 有使用編譯器的地方改成用新的編譯器名。但是假如使用
一 個(gè)變量來(lái)代替編譯器名,那么你只需要改變一個(gè)地方,其 它所有地方的命令名就都改變了。
iii) 貯存編譯器旗標(biāo)。假設(shè)你想給你所有的編譯命令傳遞一組 相同的選項(xiàng)(例 -Wall -O -g);
假如你把這組選項(xiàng)存 入一個(gè)變量,那么你可以把這個(gè)變量放在所有 呼叫編譯器 的地方。而
當(dāng)你要改變選項(xiàng)的時(shí)候,你只需在一個(gè)地方改 變這個(gè)變量的內(nèi) 容。要設(shè)定一個(gè)變量, 你只
要在一行的開始寫下這個(gè)變量的名字,后 面跟一個(gè) = 號(hào),后面 跟你要設(shè)定的這個(gè)變量的值。
以后你要引用 這個(gè)變量,寫一個(gè) $ 符號(hào),后面是圍在括 號(hào)里的變量名。比如在 下面,我們
把前面的 makefile 利用變量重寫一遍:
=== makefile 開始 ===
OBJS = foo.o bar.o
CC = gcc
CFLAGS = -Wall -O -g
myprog : $(OBJS)
$(CC) $(OBJS) -o myprog
foo.o : foo.c foo.h bar.h
$(CC) $(CFLAGS) -c foo.c -o foo.o
bar.o : bar.c bar.h
$(CC) $(CFLAGS) -c bar.c -o bar.o
=== makefile 結(jié)束 ===
還有一些設(shè)定好的內(nèi)部變量,它們根據(jù)每一個(gè)規(guī)則內(nèi)容定義。三個(gè) 比較有用的變量是
$@, $< 和 $^ (這些變量不需要括號(hào)括住)。 $@ 擴(kuò)展成當(dāng)前規(guī)則的目的文件名, $< 擴(kuò)
展成依靠列表中的第 一個(gè)依靠文件,而 $^ 擴(kuò)展成整個(gè)依靠的列表(除掉了里面所有重復(fù)
的文件名)。利用這些變量,我們可以把上面的 makefile 寫成:
=== makefile 開始 ===
OBJS = foo.o bar.o
CC = gcc
CFLAGS = -Wall -O -g
myprog : $(OBJS)
$(CC) $^ -o $@
foo.o : foo.c foo.h bar.h
$(CC) $(CFLAGS) -c $< -o $@
bar.o : bar.c bar.h
$(CC) $(CFLAGS) -c $< -o $@
=== makefile 結(jié)束 ===
你可以用變量做許多其它的事情,非凡是當(dāng)你把它們和函數(shù)混合使用的時(shí)候。假如需要
更進(jìn)一步的了解,請(qǐng)參考 GNU Make 手冊(cè)。('man make', 'man makefile')

2.4 隱含規(guī)則 (Implicit Rules)
請(qǐng)注重,在上面的例子里,幾個(gè)產(chǎn)生 .o 文件的命令都是一樣的。 都是從 .c 文件和
相關(guān)文件里產(chǎn)生 .o 文件,這是一個(gè)標(biāo)準(zhǔn)的步 驟。其實(shí) make 已經(jīng)知道怎么做——它 有一些
叫做隱含規(guī)則的內(nèi) 置的規(guī)則,這些規(guī)則告訴它當(dāng)你沒有給出某些命令的時(shí)候, 應(yīng)該怎么辦。
假如你把生成 foo.o 和 bar.o 的命令從它們的規(guī)則中刪除, make 將會(huì)查找它的隱含規(guī)則,
然后會(huì)找到一個(gè)適當(dāng)?shù)拿睢K拿顣?huì) 使用一些變量,因此你可以按照你的 想法來(lái)設(shè)定
它:它使用變量 CC 做為編譯器(象我們?cè)谇懊娴睦樱⑶覀鬟f變量 CFLAGS (給 C
編譯器,C++ 編譯器用 CXXFLAGS ),CPPFLAGS ( C 預(yù) 處理器旗 標(biāo)), TARGET_ARCH
(現(xiàn)在不用考慮這個(gè)),然后它加 入旗標(biāo) '-c' ,后面跟變量 $< (第一個(gè)依靠名),然
后是旗 標(biāo) '-o' 跟變量 $@ (目的文件名)。
一個(gè)C編譯的 具體命令將 會(huì)是:$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@
當(dāng)然你可以按照你自己的需要來(lái)定義這些變量。這就是為什么用 gcc 的 -M 或 -MM 開
關(guān)輸出的碼可以直接用在一個(gè) makefile 里。 2.5 假象目的 (Phony Targets) 假設(shè)你的一
個(gè)項(xiàng)目最后需要產(chǎn)生兩個(gè)可執(zhí)行文件。你的主要目標(biāo) 是產(chǎn)生兩個(gè)可執(zhí)行文 件,但這兩個(gè)文
件是相互獨(dú)立的——假如一個(gè)文件需要重建,并不影響另一個(gè)。你可以使用“假象目的”來(lái)達(dá)到
這種效果。一個(gè)假象目的跟一個(gè)正常的目的幾乎是一樣 的, 只是這個(gè)目的文件是不存在的。
因此, make 總是會(huì)假設(shè)它需要 被生成,當(dāng)把它 的依靠文件更新后,就會(huì)執(zhí)行它的規(guī)則里
的命令 行。 假如在我們的 makefile 開始處輸入:
all : exec1 exec2
其中 exec1 和 exec2 是我們做為目的的兩個(gè)可執(zhí)行文件。 make 把這個(gè) 'all' 做為
它的主要目的,每次執(zhí)行時(shí)都會(huì)嘗試把 'all' 更新。但既然這行規(guī)則里沒有哪個(gè)命令來(lái)作
用在一個(gè)叫 'all' 的 實(shí)際文件(事實(shí)上 all 并不會(huì)在磁碟上實(shí)際產(chǎn)生),所以這個(gè)規(guī)則
并不真的改變 'all' 的狀態(tài)。可既然這個(gè)文件并不存在,所以 make 會(huì)嘗試 更新 all 規(guī)
則,因此就檢查它的依靠 exec1, exec2 是否需要更新,假如需要,就把 它們更新,從而
達(dá)到我們的目的。假象目的也可以用來(lái)描述一組非預(yù)設(shè)的動(dòng)作。例如,你想把所有由make
產(chǎn)生的文件刪 除,你可以在 makefile 里設(shè)立這樣一個(gè)規(guī)則:
veryclean :
rm *.o
rm myprog
前提是沒有其它的規(guī)則依靠這個(gè) 'veryclean' 目的,它將永遠(yuǎn) 不會(huì)被執(zhí)行。但是,
假如你明確的使用命令 'make veryclean' , make 會(huì)把這個(gè)目的做為它的主要目標(biāo),執(zhí)行
那些 rm 命令。假如你的磁碟上存在一個(gè)叫 veryclean 文件,會(huì)發(fā)生什么事?這 時(shí)因?yàn)樵?br />
這個(gè)規(guī)則里 沒有任何依靠文件,所以這個(gè)目的文件一定是 最新的了(所有的依靠文件都已
經(jīng)是最 新的了),所以既使用戶明 確命令make重新產(chǎn)生它,也不會(huì)有任何事情發(fā)生。解決
方法是標(biāo) 明所有的假象目的(用 .PHONY),這就告訴 make 不用檢查它們 是否存在 于磁
碟上,也不用查找任何隱含規(guī)則,直接假設(shè)指定的目 的需要被更新。在 makefile 里加入
下面這行包含上面規(guī)則的規(guī)則:
..PHONY : veryclean
就可以了。注重,這是一個(gè)非凡的 make 規(guī)則,make 知道 .PHONY 是一個(gè)非凡目的,
當(dāng)然你可以在它的依靠里加入你想用的任何假象 目的,而 make 知道它們都是假象目 的。

2.6 函數(shù) (Functions)
makefile 里的函數(shù)跟它的變量很相似——使用的時(shí)候,你用一個(gè)$符號(hào)跟開括號(hào),函數(shù)
名,空格后跟一列由逗號(hào)分隔的參數(shù),最后 用關(guān)括號(hào)結(jié)束。例如,在 GNU Make 里 有一
個(gè)叫 'wildcard' 的函 數(shù),它有一個(gè)參數(shù),功能是展開成一列所有符合由其參數(shù) 描述的
文 件名,文件間以空格間隔。你可以像下面所示使用這個(gè)命令:
SOURCES = $(wildcard *.c)
這行會(huì)產(chǎn)生一個(gè)所有以 '.c' 結(jié)尾的文件的列表,然后存入變量 SOURCES 里。當(dāng)然
你不需要一定要把結(jié)果存入一個(gè)變量。
另一個(gè)有用的函數(shù)是 patsubst ( patten substitude, 匹配替 換的縮寫)函數(shù)。
它 需要3個(gè)參數(shù)——第一個(gè)是一個(gè)需要匹配的 式樣,第二個(gè)表示用什么來(lái)替換它,第三
個(gè)是一個(gè)需要被處理的 由空格分隔的字列。例如,處理那個(gè)經(jīng)過(guò)上面定義后的變量,
OBJS = $(patsubst %.c,%.o,$(SOURCES))
這行將處理所有在 SOURCES 字列中的字(一列文件名),假如它的 結(jié)尾是 '.c' ,
就 用 '.o' 把 '.c' 取代。注重這里的 % 符號(hào)將匹 配一個(gè)或多個(gè)字符,而它每次所匹
配的字串叫做一個(gè)‘柄’(stem) 。 在第二個(gè)參數(shù)里, % 被解讀成用第一參數(shù)所匹配的 那個(gè)柄。

2.7 一個(gè)比較有效的 makefile
利用我們現(xiàn)在所學(xué)的,我們可以建立一個(gè)相當(dāng)有效的 makefile 。 這個(gè) makefile
可 以完成大部分我們需要的依靠檢查,不用做太大 的改變就可直接用在大多數(shù)的項(xiàng)目里。
首先我們需要一個(gè)基本的 makefile 來(lái)建我們的程序。我們可以讓 它搜索當(dāng)前目錄,
找到源碼文件,并且假設(shè)它們都是屬于我們的項(xiàng) 目的,放進(jìn)一個(gè)叫 SOURCES 的變量。
這里假如也包含所有的 *.cc 文件,也許會(huì)更保險(xiǎn),因?yàn)樵创a文件可能是 C++ 碼的。
SOURCES = $(wildcard *.c *.cc) 利用 patsubst ,我們可以由源碼文件名產(chǎn)生目標(biāo)文
件名,我們需 要編譯出這些目標(biāo) 文件。假如我們的源碼文件既有 .c 文件,也有 .cc 文件,
我們需要使用相嵌的 patsubst 函數(shù)呼叫:
OBJS = $(patsubst %.c,%.o,$(patsubst %.cc,%.o,$(SOURCES)))
最里面一層 patsubst 的呼叫會(huì)對(duì) .cc 文件進(jìn)行后綴替代,產(chǎn)生的結(jié) 果被外層的
patsubst 呼叫處理,進(jìn)行對(duì) .c 文件后綴的替代。
現(xiàn)在我們可以設(shè)立一個(gè)規(guī)則來(lái)建可執(zhí)行文件:
myprog : $(OBJS)
gcc -o myprog $(OBJS)
進(jìn)一步的規(guī)則不一定需要, gcc 已經(jīng)知道怎么去生成目標(biāo)文件 (object files) 。下面我們可以設(shè)定產(chǎn)生依靠信息的規(guī)則:
depends : $(SOURCES)
gcc -M $(SOURCES) > depends
在這里假如一個(gè)叫 'depends' 的文件不存在,或任何一個(gè)源碼文件 比一個(gè)已存在的
depends 文件新,那么一個(gè) depends 文件會(huì)被生 成。depends 文件將會(huì)含有由 gcc產(chǎn)生
的關(guān)于源碼文件的規(guī)則(注重-M開關(guān))。現(xiàn)在我們要讓 make 把這些規(guī)則當(dāng)做 makefile
檔 的一部分。這里使用的技巧很像 C 語(yǔ)言中的 #include 系統(tǒng)——我 們要 求 make 把這
個(gè)文件 include 到 makefile 里,如下:
include depends
GNU Make 看到這個(gè),檢查 'depends' 目的是否更新了,假如沒有, 它用我們給它
的命令重新產(chǎn)生 depends 檔。然后它會(huì)把這組(新) 規(guī)則包含進(jìn)來(lái),繼續(xù)處理最終目標(biāo)
'myprog' 。當(dāng)看到有關(guān) myprog 的規(guī)則,它會(huì)檢查所有的目標(biāo)文件是否更新——利用 depends
文件 里的規(guī)則,當(dāng)然這些規(guī)則現(xiàn)在已經(jīng)是更新過(guò)的了。
這個(gè)系統(tǒng)其實(shí)效率很低,因?yàn)槊慨?dāng)一個(gè)源碼文件被改動(dòng),所有的源碼 文件都要被預(yù)處
理以產(chǎn)生一個(gè)新的 'depends' 文件。而且它也不是 100% 的安全,這是因?yàn)楫?dāng)一個(gè) header
檔被改動(dòng),依靠信息并不會(huì) 被更新。但就基本工作來(lái)說(shuō),它也算相當(dāng)有用的 了。

2.8 一個(gè)更好的 makefile
這是一個(gè)我為我大多數(shù)項(xiàng)目設(shè)計(jì)的 makefile 。它應(yīng)該可以不需要修 改的用在大部分
項(xiàng)目里。我主要把它用在 djgpp 上,那是一個(gè) DOS 版的 gcc 編譯器。因此你可以看到執(zhí)
行的命令名、 'alleg' 程序包、 和 RM -F 變量都反映了這一點(diǎn)。
=== makefile 開始 ===
######################################
#
# Generic makefile

#
# by George Foot
# email: george.foot@merton.ox.ac.uk
#
# Copyright (c) 1997 George Foot
# All rights reserved.
# 保留所有版權(quán)
#
# No warranty, no liability;
# you use this at your own risk.
# 沒保險(xiǎn),不負(fù)責(zé)
# 你要用這個(gè),你自己擔(dān)風(fēng)險(xiǎn)
#
# You are free to modify and
# distribute this without giving
# credit to the original author.
# 你可以隨便更改和散發(fā)這個(gè)文件
# 而不需要給原作者什么榮譽(yù)。
# (你好意思?)
#
######################################
### Customising
# 用戶設(shè)定
#
# Adjust the following if necessary; EXECUTABLE is the target
# executable's filename, and LIBS is a list of libraries to link in
# (e.g. alleg, stdcx, iostr, etc). You can override these on make's
# command line of course, if you prefer to do it that way.
#
# 假如需要,調(diào)整下面的東西。 EXECUTABLE 是目標(biāo)的可執(zhí)行文件名, LIBS
# 是一個(gè)需要連接的程序包列表(例如 alleg, stdcx, iostr 等等)。當(dāng)然你
# 可以在 make 的命令行覆蓋它們,你愿意就沒問(wèn)題。
#
EXECUTABLE := mushroom.exe
LIBS := alleg
# Now alter any implicit rules' variables if you like, e.g.:
#
# 現(xiàn)在來(lái)改變?nèi)魏文阆敫膭?dòng)的隱含規(guī)則中的變量,例如
CFLAGS := -g -Wall -O3 -m486
CXXFLAGS := $(CFLAGS)
# The next bit checks to see whether rm is in your djgpp bin
# Directory; if not it uses del instead, but this can cause (harmless)
# `File not found' error messages. If you are not using DOS at all,
# set the variable to something which will unquestioningly remove
# files.
#
# 下面先檢查你的 djgpp 命令目錄下有沒有 rm 命令,假如沒有,我們使用 del 命令來(lái)代替,但有可能給我們 'File not found' 這個(gè)錯(cuò)誤信息,這沒 # 什么大礙。假如你不是用 DOS ,把它設(shè)定成一個(gè)刪文件而不廢話的命令。 (其實(shí)這一步在 UNIX 類的系統(tǒng)上是多余的,只是方便 DOS 用戶。 UNIX 用戶可以刪除這5行命令。)

ifneq ($(wildcard $(DJDIR)/bin/rm.exe),)
RM-F := rm -f
else
RM-F := del
endif
# You shouldn't need to change anything below this point.
#
# 從這里開始,你應(yīng)該不需要改動(dòng)任何東西。(我是不太相信,太NB了!)
SOURCE := $(wildcard *.c) $(wildcard *.cc)
OBJS := $(patsubst %.c,%.o,$(patsubst %.cc,%.o,$(SOURCE)))
DEPS := $(patsubst %.o,%.d,$(OBJS))
MISSING_DEPS := $(filter-out $(wildcard $(DEPS)),$(DEPS))
MISSING_DEPS_SOURCES := $(wildcard $(patsubst %.d,%.c,$(MISSING_DEPS))
$(patsubst %.d,%.cc,$(MISSING_DEPS)))
CPPFLAGS += -MD
..PHONY : everything deps objs clean veryclean rebuild
everything : $(EXECUTABLE)
deps : $(DEPS)
objs : $(OBJS)
clean :
@$(RM-F) *.o
@$(RM-F) *.d
veryclean: clean
@$(RM-F) $(EXECUTABLE)
rebuild: veryclean everything
ifneq ($(MISSING_DEPS),)
$(MISSING_DEPS) :
@$(RM-F) $(patsubst %.d,%.o,$@)
endif
-include $(DEPS)
$(EXECUTABLE) : $(OBJS)
gcc -o $(EXECUTABLE) $(OBJS) $(addprefix -l,$(LIBS))
=== makefile 結(jié)束 ===


有幾個(gè)地方值得解釋一下的。首先,我在定義大部分變量的時(shí)候使 用的是 :=
而不是=符號(hào)。它的作用是立即把定義中參考到的函 數(shù)和變量都展開了。假如使用=
的話,函數(shù)和變量參考會(huì)留在那 兒,就是說(shuō)改變一個(gè)變量的值會(huì)導(dǎo)致其它變量的值
也被改變。例如:
A = foo
B = $(A)
# 現(xiàn)在 B 是 $(A) ,而 $(A) 是 'foo' 。
A = bar
# 現(xiàn)在 B 仍然是 $(A) ,但它的值已隨著變成 'bar' 了。
B := $(A)
# 現(xiàn)在 B 的值是 'bar' 。
A = foo
# B 的值仍然是 'bar' 。
make 會(huì)忽略在 # 符號(hào)后面直到那一行結(jié)束的所有文字。
ifneg...else...endif 系統(tǒng)是 makefile 里讓某一部分碼有條件的 失效/有效的工
具。 ifeq 使用兩個(gè)參數(shù),假如它們相同,它把直 到 else (或者 endif ,假如沒有
else 的話)的一段碼加進(jìn) makefile 里;假如不同,把 else 到 endif 間的一段碼加入
makefile (假如有 else )。 ifneq 的用法剛好相反。 'filter-out' 函數(shù)使用兩個(gè)用
空格分開的列表,它把第二列表中所 有的存在于第一列 表中的項(xiàng)目刪除。我用它來(lái)處理
DEPS 列表,把所 有已經(jīng)存在的項(xiàng)目都刪除,而只保留缺少的那些。
我前面說(shuō)過(guò), CPPFLAGS 存有用于隱含規(guī)則中傳給預(yù)處理器的一些 旗標(biāo)。而 -MD 開
關(guān) 類似 -M 開關(guān),但是從源碼文件 .c 或 .cc 中 形成的文件名是使用后綴.d 的(這就
解釋了我形成 DEPS 變量的 步驟)。DEPS 里提到的文件后來(lái)用 '-include' 加進(jìn)了 makefile
里,它隱藏了所有因文件不存在而產(chǎn)生的錯(cuò)誤信息。 假如任何依靠文件不存在, makefile
會(huì)把相應(yīng)的 .o 文件從磁碟 上刪除,從而使得 make 重建它。因?yàn)?CPPFLAGS 指定了-MD,
它的 .d 文件也被重新產(chǎn)生。 最后, 'addprefix' 函數(shù)把第二個(gè)參數(shù)列表的每一項(xiàng)前綴
上第一個(gè)參數(shù)值。這個(gè)makefile的那些目的是(這些目的可以傳給make的命令行來(lái)直接選用):
everything:(預(yù)設(shè)) 更新主要的可執(zhí)行程序,并且為每一個(gè) 源碼文件生成或更新一個(gè) '.d'
文件和一個(gè) '.o' 文件。 deps: 只是為每一個(gè)源碼程序產(chǎn)生或更新一個(gè) '.d' 文件。
objs: 為每一個(gè)源碼程序生成或更新 '.d' 文件和目標(biāo)文件。 clean: 刪除所有中介/
依靠文件( *.d 和 *.o )。
veryclean: 做 `clean' 和刪除可執(zhí)行文件。
rebuild: 先做 `veryclean' 然后 `everything' ;既完全重建。
除了預(yù)設(shè)的 everything 以外,這里頭只有 clean , veryclean , 和rebuild 對(duì)用戶是
有意義的。 我還沒有發(fā)現(xiàn)當(dāng)給出一個(gè)源碼文件的目錄,這個(gè) makefile 會(huì)失敗的情況,除非依
靠文件被弄亂。假如這種弄亂的情況發(fā)生了,只要輸入 `make clean' , 所有的目標(biāo)文件和依
靠文件會(huì)被刪除,問(wèn)題就應(yīng)該 被解決了。當(dāng)然,最好不要把它們弄亂。假如你發(fā)現(xiàn)在某種情況
下這個(gè)makefile 文件不能完成它的工作,請(qǐng)告訴我,我會(huì)把它整好的。
3 總結(jié)
我希望這篇文章足夠具體的解釋了多文件項(xiàng)目是怎么運(yùn)作的,也說(shuō)明了 怎樣安全而合理的
使用它。到此,你應(yīng)該可以輕松的利用 GNU Make 工 具來(lái)治理小型的項(xiàng)目,假如你完全理解了
后面幾個(gè)部分的話,這些對(duì)于 你來(lái)說(shuō)應(yīng)該沒什么困難。 GNU Make 是一件強(qiáng)大的工具,雖然它
主要是用來(lái)建立程序,它還有很多 別的用處。假如想要知道更多有關(guān)這個(gè)工具的知識(shí),它的句
法,函數(shù),和許多別的特點(diǎn),你應(yīng)該參看它的參考文件(info pages, 別的GNU工具也一樣, 看
它們的 info pages. )。


文章摘要:
  無(wú)論是在linux還是在Unix環(huán)境中,make都是一個(gè)非常重要的編譯命令。不管是自己進(jìn)行項(xiàng)
目開發(fā)還是安裝應(yīng)用軟件,我們都經(jīng)常要用到make或make install,有效的利用make和makefile
工具可以大大提高項(xiàng)目開發(fā)的效率。但令人遺憾的是,在許多講述linux應(yīng)用的書籍上都沒有具體
介紹這個(gè)功能強(qiáng)大但又非常復(fù)雜的編譯工具。在這里我就向大家具體介紹一下make及其描述文件makefile。
<http://member.netease.com/%7Ehalon/unixfaq/unix_31.htm>

正文:  
linux/Unix環(huán)境下的make和makefile詳解  
Pathetique

  無(wú)論是在linux還是在Unix環(huán)境中,make都是一個(gè)非常重要的編譯命令。不管是自己進(jìn)行項(xiàng)
目開發(fā)還是安裝應(yīng)用軟件, 我們都經(jīng)常要用到make或make install。利用make工具,我們可以
將大型的開發(fā)項(xiàng)目分解成為多個(gè)更易于治理的模塊, 對(duì)于一個(gè)包括幾百個(gè)源文件的應(yīng)用程序,
使用make和makefile工具就可以簡(jiǎn)潔明快地理順各個(gè)源文件之間紛繁復(fù)雜的相互關(guān)系。 而且如
此多的源文件,假如每次都要鍵入gcc命令進(jìn)行編譯的話,那對(duì)程序員來(lái)說(shuō)簡(jiǎn)直就是一場(chǎng)災(zāi)難。
而make工具則可自動(dòng)完成編譯工作,并且可以只對(duì)程序員在上次編譯后修改過(guò)的部分進(jìn)行編譯。

因此,有效的利用make和makefile工具可以大大提高項(xiàng)目開發(fā)的效率。同時(shí)把握make和makefile
之后,您也不會(huì)再面對(duì)著linux下的應(yīng)用軟件手足無(wú)措了。
  但令人遺憾的是,在許多講述linux應(yīng)用的書籍上都沒有具體介紹這個(gè)功能強(qiáng)大但又非常復(fù)
雜的編譯工具。在這里我就向大家具體介紹一下make及其描述文件makefile。

Makefile文件

  Make工具最主要也是最基本的功能就是通過(guò)makefile文件來(lái)描述源程序之間的相互關(guān)系并自
動(dòng)維護(hù)編譯工作。而makefile文件需要按照某種語(yǔ)法進(jìn)行編寫,文件中需要說(shuō)明如何編譯各個(gè)源
文件并連接生成可執(zhí)行文件,并要求定義源文件之間的依靠關(guān)系。makefile文件是許多編譯器--
包括 Windows NT 下的編譯器--維護(hù)編譯信息的常用方法,只是在集成開發(fā)環(huán)境中,用戶通過(guò)友
好的界面修改 makefile 文件而已。
  在UNIX系統(tǒng)中,習(xí)慣使用Makefile作為makfile文件。假如要使用其他文件作為 makefile,
則可利用類似下面的 make 命令選項(xiàng)指定 makefile 文件:
  $ make -f Makefile.debug
  例如,一個(gè)名為prog的程序由三個(gè)C源文件filea.c、fileb.c和filec.c以及庫(kù)文件LS編譯生
成,這三個(gè)文件還分別包含自己的頭文件a.h 、b.h和c.h。通常情況下, C編譯器將會(huì)輸出三個(gè)
目標(biāo)文件filea.o、fileb.o和filec.o。 假設(shè)filea.c和fileb.c都要聲明用到一個(gè)名為defs的文
件,但filec.c不用。即在filea.c和fileb.c里都有這樣的聲明:
  #include "defs"
  那么下面的文檔就描述了這些文件之間的相互聯(lián)系:
  ---------------------------------------------------------
   #It is a example for describing makefile
   prog : filea.o fileb.o filec.o
   cc filea.o fileb.o filec.o -LS -o prog
   filea.o : filea.c a.h defs
   cc -c filea.c
   fileb.o : fileb.c b.h defs
   cc -c fileb.c
   filec.o : filec.c c.h
   cc -c filec.c
  ----------------------------------------------------------
  這個(gè)描述文檔就是一個(gè)簡(jiǎn)單的makefile文件。
  從上面的例子注重到,第一個(gè)字符為 # 的行為注釋行。第一個(gè)非注釋行指定prog由三個(gè)目
標(biāo)文件filea.o、fileb.o和filec.o鏈接生成。第三行描述了如何從prog所依靠的文件建立可執(zhí)
行文件。接下來(lái)的4、6、8行分別指定三個(gè)目標(biāo)文件,以及它們所依靠的.c和.h文件以及defs文
件。而5、7、9行則指定了如何從目標(biāo)所依靠的文件建立目標(biāo)。
  當(dāng)filea.c或a.h文件在編譯之后又被修改,則 make 工具可自動(dòng)重新編譯filea.o,假如在
前后兩次編譯之間,filea.C 和a.h 均沒有被修改,而且 test.o 還存在的話, 就沒有必要重
新編譯。這種依靠關(guān)系在多源文件的程序編譯中尤其重要。通過(guò)這種依靠關(guān)系的定義,make 工
具可避免許多不必要的編譯工作。當(dāng)然,利用 Shell 腳本也可以達(dá)到自動(dòng)編譯的效果,但是,
Shell 腳本將全部編譯任何源文件,包括哪些不必要重新編譯的源文件,而 make 工具則可根據(jù)
目標(biāo)上一次編譯的時(shí)間和目標(biāo)所依靠的源文件的更新時(shí)間而自動(dòng)判定應(yīng)當(dāng)編譯哪個(gè)源文件。
Makefile文件作為一種描述文檔一般需要包含以下內(nèi)容:
  ◆ 宏定義
  ◆ 源文件之間的相互依靠關(guān)系
  ◆ 可執(zhí)行的命令
  Makefile中答應(yīng)使用簡(jiǎn)單的宏指代源文件及其相關(guān)編譯信息,在linux中也稱宏為變量。在
引用宏時(shí)只需在變量前加$符號(hào),但值得注重的是,假如變量名的長(zhǎng)度超過(guò)一個(gè)字符,在引用時(shí)
就必須加圓括號(hào)()。
  下面都是有效的宏引用:
  $(CFLAGS)
  $2
  $Z
  $(Z)
  其中最后兩個(gè)引用是完全一致的。
  需要注重的是一些宏的預(yù)定義變量,在Unix系統(tǒng)中,$*、$@、$?和$<四個(gè)非凡宏的值在執(zhí)行
命令的過(guò)程中會(huì)發(fā)生相應(yīng)的變化,而在GNU make中則定義了更多的預(yù)定義變量。關(guān)于預(yù)定義變量
的具體內(nèi)容,
  宏定義的使用可以使我們脫離那些冗長(zhǎng)乏味的編譯選項(xiàng),為編寫makefile文件帶來(lái)很大的方便。
  ---------------------------------------------------------
   # Define a macro for the object files
   OBJECTS= filea.o fileb.o filec.o

   # Define a macro for the library file
   LIBES= -LS

   # use macros rewrite makefile
   prog: $(OBJECTS)
   cc $(OBJECTS) $(LIBES) -o prog
   ……
  ---------------------------------------------------------
  此時(shí)假如執(zhí)行不帶參數(shù)的make命令,將連接三個(gè)目標(biāo)文件和庫(kù)文件LS;但是假如在make命令后帶有新的宏定義:
  make "LIBES= -LL -LS"
則命令行后面的宏定義將覆蓋makefile文件中的宏定義。若LL也是庫(kù)文件,此時(shí)make命令將

連接三個(gè)目標(biāo)文件以及兩個(gè)庫(kù)文件LS和LL。
  在Unix系統(tǒng)中沒有對(duì)常量NULL作出明確的定義,因此我們要定義NULL字符串時(shí)要使用下述宏定義:
  STRINGNAME=

Make命令

  在make命令后不僅可以出現(xiàn)宏定義,還可以跟其他命令行參數(shù),這些參數(shù)指定了需要編譯
的目標(biāo)文件。其標(biāo)準(zhǔn)形式為:
  target1 [target2 …]:[:][dependent1 …][;commands][#…]
  [(tab) commands][#…]
  方括號(hào)中間的部分表示可選項(xiàng)。Targets和dependents當(dāng)中可以包含字符、數(shù)字、句點(diǎn)和"/"
符號(hào)。除了引用,commands中不能含有"#",也不答應(yīng)換行。
  在通常的情況下命令行參數(shù)中只含有一個(gè)":",此時(shí)command序列通常和makefile文件中某些
定義文件間依靠關(guān)系的描述行有關(guān)。假如與目標(biāo)相關(guān)連的那些描述行指定了相關(guān)的command序列,
那么就執(zhí)行這些相關(guān)的command命令,即使在分號(hào)和(tab)后面的aommand字段甚至有可能是NULL。
假如那些與目標(biāo)相關(guān)連的行沒有指定command,那么將調(diào)用系統(tǒng)默認(rèn)的目標(biāo)文件生成規(guī)則。
  假如命令行參數(shù)中含有兩個(gè)冒號(hào)"::",則此時(shí)的command序列也許會(huì)和makefile中所有描述
文件依靠關(guān)系的行有關(guān)。此時(shí)將執(zhí)行那些與目標(biāo)相關(guān)連的描述行所指向的相關(guān)命令。同時(shí)還將執(zhí)
行build-in規(guī)則。
  假如在執(zhí)行command命令時(shí)返回了一個(gè)非"0"的出錯(cuò)信號(hào),例如makefile文件中出現(xiàn)了錯(cuò)誤的
目標(biāo)文件名或者出現(xiàn)了以連字符打頭的命令字符串,make操作一般會(huì)就此終止,但假如make后帶
有"-i"參數(shù),則make將忽略此類出錯(cuò)信號(hào)。
  Make命本身可帶有四種參數(shù):標(biāo)志、宏定義、描述文件名和目標(biāo)文件名。其標(biāo)準(zhǔn)形式為:
  Make [flags] [macro definitions] [targets]
  Unix系統(tǒng)下標(biāo)志位flags選項(xiàng)及其含義為:
  -f file  指定file文件為描述文件,假如file參數(shù)為"-"符,那么描述文件指向標(biāo)準(zhǔn)輸入。
假如沒有"-f"參數(shù),則系統(tǒng)將默認(rèn)當(dāng)前目錄下名為makefile或者名為Makefile的文件為描述文件。
在linux中, GNU make 工具在當(dāng)前工作目錄中按照GNUmakefile、makefile、Makefile的順序搜索
makefile文件。
  -i   忽略命令執(zhí)行返回的出錯(cuò)信息。
  -s   沉默模式,在執(zhí)行之前不輸出相應(yīng)的命令行信息。
  -r   禁止使用build-in規(guī)則。
  -n   非執(zhí)行模式,輸出所有執(zhí)行命令,但并不執(zhí)行。
  -t   更新目標(biāo)文件。
  -q   make操作將根據(jù)目標(biāo)文件是否已經(jīng)更新返回"0"或非"0"的狀態(tài)信息。
  -p   輸出所有宏定義和目標(biāo)文件描述。
  -d   Debug模式,輸出有關(guān)文件和檢測(cè)時(shí)間的具體信息。
  linux下make標(biāo)志位的常用選項(xiàng)與Unix系統(tǒng)中稍有不同,下面我們只列出了不同部分:
  -c dir   在讀取 makefile 之前改變到指定的目錄dir。
  -I dir   當(dāng)包含其他 makefile文件時(shí),利用該選項(xiàng)指定搜索目錄。
  -h   help文擋,顯示所有的make選項(xiàng)。
  -w   在處理 makefile 之前和之后,都顯示工作目錄。
  通過(guò)命令行參數(shù)中的target,可指定make要編譯的目標(biāo),并且答應(yīng)同時(shí)定義編譯多個(gè)目標(biāo),
操作時(shí)按照從左向右的順序依次編譯target選項(xiàng)中指定的目標(biāo)文件。假如命令行中沒有指定目標(biāo),
則系統(tǒng)默認(rèn)target指向描述文件中第一個(gè)目標(biāo)文件。
  通常,makefile 中還定義有 clean 目標(biāo),可用來(lái)清除編譯過(guò)程中的中間文件,例如:
  clean:
  rm -f *.o
  運(yùn)行 make clean 時(shí),將執(zhí)行 rm -f *.o 命令,最終刪除所有編譯過(guò)程中產(chǎn)生的所有中間文件。

隱含規(guī)則
  在make 工具中包含有一些內(nèi)置的或隱含的規(guī)則, 這些規(guī)則定義了如何從不同的依靠文件建
立特定類型的目標(biāo)。Unix系統(tǒng)通常支持一種基于文件擴(kuò)展名即文件名后綴的隱含規(guī)則。這種后綴
規(guī)則定義了如何將一個(gè)具有特定文件名后綴的文件(例如.c文件),轉(zhuǎn)換成為具有另一種文件名
后綴的文件(例如.o文件):
  .c:.o
  $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
  系統(tǒng)中默認(rèn)的常用文件擴(kuò)展名及其含義為:
  .o  目標(biāo)文件
  .c  C源文件
  .f  FORTRAN源文件
  .s  匯編源文件
  .y  Yacc-C源語(yǔ)法
  .l  Lex源語(yǔ)法
  在早期的Unix系統(tǒng)系統(tǒng)中還支持Yacc-C源語(yǔ)法和Lex源語(yǔ)法。在編譯過(guò)程中, 系統(tǒng)會(huì)首先在
makefile文件中尋找與目標(biāo)文件相關(guān)的.C文件,假如還有與之相依靠的.y和.l文件,則首先將其
轉(zhuǎn)換為.c文件后再編譯生成相應(yīng)的.o文件;假如沒有與目標(biāo)相關(guān)的.c文件而只有相關(guān)的.y文件,
則系統(tǒng)將直接編譯.y文件。
  而GNU make 除了支持后綴規(guī)則外還支持另一種類型的隱含規(guī)則--模式規(guī)則。 這種規(guī)則更加
通用,因?yàn)榭梢岳媚J揭?guī)則定義更加復(fù)雜的依靠性規(guī)則。模式規(guī)則看起來(lái)非常類似于正則規(guī)則,
但在目標(biāo)名稱的前面多了一個(gè) %號(hào),同時(shí)可用來(lái)定義目標(biāo)和依靠文件之間的關(guān)系,例如下面的模
式規(guī)則定義了如何將任意一個(gè) file.c 文件轉(zhuǎn)換為 file.o 文件:
  %.c:%.o
  $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<

#EXAMPLE#

  下面將給出一個(gè)較為全面的示例來(lái)對(duì)makefile文件和make命令的執(zhí)行進(jìn)行進(jìn)一步的說(shuō)明,其

中make命令不僅涉及到了C源文件還包括了Yacc語(yǔ)法。本例選自"Unix Programmer's Manual 7th
Edition, Volume 2A" Page 283-284
  下面是描述文件的具體內(nèi)容:
  ---------------------------------------------------------
   #Description file for the Make command
   #Send to print
   P=und -3 opr -r2
   #The source files that are needed by object files
   FILES= Makefile version.c defs main.c donamc.c misc.c file.c
   dosys.c gram.y lex.c gcos.c
   #The definitions of object files
   OBJECTS= vesion.o main.o donamc.o misc.o file.o dosys.o gram.o
   LIBES= -LS
   LINT= lnit -p
   CFLAGS= -O
   make: $(OBJECTS)
   cc $(CFLAGS) $(OBJECTS) $(LIBES) -o make
   size make
   $(OBJECTS): defs
   gram.o: lex.c
   cleanup:
   -rm *.o gram.c
   install:
   @size make /usr/bin/make
   cp make /usr/bin/make ; rm make
   #print recently changed files
   print: $(FILES)
   pr $? $P
   toUCh print
   test:
   make -dp grep -v TIME>1zap
   /usr/bin/make -dp grep -v TIME>2zap
   diff 1zap 2zap
   rm 1zap 2zap
   lint: dosys.c donamc.c file.c main.c misc.c version.c gram.c
   $(LINT) dosys.c donamc.c file.c main.c misc.c version.c
   gram.c
   rm gram.c
   arch:
   ar uv /sys/source/s2/make.a $(FILES)
  ----------------------------------------------------------
  通常在描述文件中應(yīng)象上面一樣定義要求輸出將要執(zhí)行的命令。在執(zhí)行了make命令之后,輸
出結(jié)果為:
  $ make
  cc -c version.c
  cc -c main.c
  cc -c donamc.c
  cc -c misc.c
  cc -c file.c
  cc -c dosys.c
  yacc gram.y
  mv y.tab.c gram.c
  cc -c gram.c
  cc version.o main.o donamc.o misc.o file.o dosys.o gram.o
  -LS -o make
  13188+3348+3044=19580b=046174b

  最后的數(shù)字信息是執(zhí)行"@size make"命令的輸出結(jié)果。之所以只有輸出結(jié)果而沒有相應(yīng)的命
令行,是因?yàn)?@size make"命令以"@"起始,這個(gè)符號(hào)禁止打印輸出它所在的命令行。
  描述文件中的最后幾條命令行在維護(hù)編譯信息方面非常有用。 其中"print"命令行的作用是
打印輸出在執(zhí)行過(guò)上次"make print"命令后所有改動(dòng)過(guò)的文件名稱。 系統(tǒng)使用一個(gè)名為print的
0字節(jié)文件來(lái)確定執(zhí)行print命令的具體時(shí)間, 而宏$?則指向那些在print文件改動(dòng)過(guò)之后進(jìn)行修
改的文件的文件名。假如想要指定執(zhí)行print命令后,將輸出結(jié)果送入某個(gè)指定的文件, 那么就
可修改P的宏定義:
  make print "P= cat>zap"
  在linux中大多數(shù)軟件提供的是源代碼,而不是現(xiàn)成的可執(zhí)行文件, 這就要求用戶根據(jù)自己
系統(tǒng)的實(shí)際情況和自身的需要來(lái)配置、編譯源程序后,軟件才能使用。只有把握了make工具,才
能讓我們真正享受到到linux這個(gè)自由軟件世界的帶給我們無(wú)窮樂(lè)趣。

上一篇:GCC

下一篇:編程資源收集

發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 克山县| 旬阳县| 商洛市| 东港市| 凤庆县| 临朐县| 南召县| 五寨县| 怀安县| 鄂州市| 丁青县| 云霄县| 闻喜县| 皮山县| 高邑县| 海南省| 民丰县| 房产| 肃北| 库车县| 泗水县| 阿拉善右旗| 北川| 普兰县| 汤阴县| 博客| 尚义县| 怀宁县| 乐业县| 铜梁县| 阿拉尔市| 耿马| 藁城市| 西青区| 仪征市| 元氏县| 永定县| 古交市| 安阳县| 南华县| 韶关市|