linux gun make 入門(mén)

本文是一篇介紹 GNU Make 的文章镀钓,讀完后讀者應(yīng)該基本掌握了 make 的用法。make 是所有想在 Unix/ Linux 系統(tǒng)上編程的用戶必須掌握的工具。

1.介紹

本文將首先介紹為什么要將你的C源代碼分離成幾個(gè)合理的獨(dú)立檔案,什么時(shí)候需要分盟广,怎么才能分的好。然后將會(huì)告訴你 GNU Make 怎樣使你的編譯和連 接步驟自動(dòng)化瓮钥。

2.多文件項(xiàng)目

2.1為什么使用它們?
首先筋量,多文件項(xiàng)目的好處在那里呢?
它們看起來(lái)把事情弄的復(fù)雜無(wú)比碉熄。又要 header 文件桨武,又要 extern 聲明,而且如果需要查找一個(gè)文件锈津,你要在更多的文件里搜索呀酸。

但其實(shí)我們有很有力的理由支持我們把一個(gè)項(xiàng)目分解成小塊。當(dāng)你改 動(dòng)一行代碼琼梆,編譯器需要全部重新編譯來(lái)生成一個(gè)新的可執(zhí)行文件性誉。 但如果你的項(xiàng)目是分開(kāi)在幾個(gè)小文件里,當(dāng)你改動(dòng)其中一個(gè)文件的時(shí) 候茎杂,別的源文件的目標(biāo)文件(object files)已經(jīng)存在错览,所以沒(méi)有什么 原因去重新編譯它們。你所需要做的只是重現(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ì)嘗試不去連接沒(méi)有使 用到的部分橙喘。但它每次只能從程序包中把一個(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化的彭雾,文件之間的共享部分被減到最少,那 就有很多好處——可以很容易的追蹤到臭蟲(chóng)锁保,這些模塊經(jīng)常是可以用 在其它的項(xiàng)目里的薯酝,同時(shí)別人也可以更容易的理解你的一段代碼是干 什么的。當(dāng)然此外還有許多別的好處……

1.2 何時(shí)分解你的項(xiàng)目

很明顯爽柒,把任何東西都分解是不合理的吴菠。象“世界,你們好”這樣的 簡(jiǎn)單程序根本就不能分浩村,因?yàn)閷?shí)在也沒(méi)什么可分的做葵。把用于測(cè)試用的 小程序分解也是沒(méi)什么意思的。但一般來(lái)說(shuō)心墅,當(dāng)分解項(xiàng)目有助于布局酿矢、 發(fā)展和易讀性的時(shí)候,我都會(huì)采取它怎燥。在大多數(shù)的情況下瘫筐,這都是適 用的。(所謂“世界刺覆,你們好”严肪,既 'hello world' ,只是一個(gè)介 紹一種編程語(yǔ)言時(shí)慣用的范例程序,它會(huì)在屏幕上顯示一行 'hello world' 驳糯。是最簡(jiǎn)單的程序篇梭。)

如果你需要開(kāi)發(fā)一個(gè)相當(dāng)大的項(xiàng)目,在開(kāi)始前酝枢,應(yīng)該考慮一下你將 如何實(shí)現(xiàn)它恬偷,并且生成幾個(gè)文件(用適當(dāng)?shù)拿郑﹣?lái)放你的代碼。 當(dāng)然帘睦,在你的項(xiàng)目開(kāi)發(fā)的過(guò)程中袍患,你可以建立新的文件,但如果你 這么做的話竣付,說(shuō)明你可能改變了當(dāng)初的想法诡延,你應(yīng)該想想是否需要 對(duì)整體結(jié)構(gòu)也進(jìn)行相應(yīng)的調(diào)整。

對(duì)于中型的項(xiàng)目古胆,你當(dāng)然也可以采用上述技巧肆良,但你也可以就那么開(kāi) 始輸入你的代碼,當(dāng)你的碼多到難以管理的時(shí)候再把它們分解成不同 的檔案逸绎。但以我的經(jīng)驗(yàn)來(lái)說(shuō)惹恃,開(kāi)始時(shí)在腦子里形成一個(gè)大概的方案, 并且盡量遵從它棺牧,或在開(kāi)發(fā)過(guò)程中巫糙,隨著程序的需要而修改,會(huì)使開(kāi) 發(fā)變得更加容易颊乘。

1.3 怎樣分解項(xiàng)目

先說(shuō)明参淹,這完全是我個(gè)人的意見(jiàn),你可以(也許你真的會(huì)疲牵?)用別的 方式來(lái)做承二。這會(huì)觸動(dòng)到有關(guān)編碼風(fēng)格的問(wèn)題,而大家從來(lái)就沒(mé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í)將不可公開(kāi)調(diào)用的函數(shù)原型家妆,類型定義 等等,從它們的C源碼文件中分離出來(lái)是非常有用的冕茅。使用一 個(gè) header 文件裝公開(kāi)符號(hào)伤极,用另一個(gè)裝私人符號(hào)意味著如果 你改變了這個(gè)源碼文件的內(nèi)部結(jié)構(gòu)蛹找,你可以只是重新編譯它而 不需要重新編譯那些使用它的公開(kāi) 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ì)于常見(jiàn)錯(cuò)誤的注釋

a) 定義符 (Identifier) 在源碼文件中的矛盾:在C里,變量和函數(shù)的缺 省狀態(tài)是公用的茶凳。因此礁凡,任何C源碼檔案都可以引用存在于其它源 碼檔中的通用 (global) 函數(shù)和通用變量,既使這個(gè)檔案沒(méi)有那個(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)在它被定義的源文件以外被看到套耕,可在它的定義前 加上關(guān)鍵字“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ò)誤(見(jiàn) 上)征冷。

解決方法: 不要在 header 檔里定義變量。你只需要在 header 檔里聲明它們?nèi)缓笤谶m當(dāng)?shù)模迷创a文件(應(yīng)該 #include 那個(gè) header 檔的那個(gè))里定義它們(一次)誓琼。對(duì)于初學(xué)者來(lái)說(shuō)检激,定義和聲明是 很容易混淆的肴捉。聲明的作用是告訴編譯器其所聲明的符號(hào)應(yīng)該存在, 并且要有所指定的類型叔收。但是齿穗,它并不會(huì)使編譯器分配貯存空間。 而定義的做用是要求編譯器分配貯存空間今穿。當(dāng)做一個(gè)聲明而不是做 定義的時(shí)候缤灵,在聲明前放一個(gè)關(guān)鍵字“extern”。

例如蓝晒,我們有一個(gè)叫“counter”的變量腮出,如果想讓它成為公用的, 我們?cè)谝粋€(gè)源碼程序(只在一個(gè)里面)的開(kāi)始定義它:“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è)巨集指令沒(méi)有被定義的前提下,我們才真正使用 該 header 檔的主體蛾狗。在實(shí)際應(yīng)用上晋涣,我們只要簡(jiǎn)單的把下面一段 碼放在每一個(gè) header 檔的開(kāi)始部分:

#ifndef FILENAME_H
#define FILENAME_H

然后把下面一行碼放在最后:

#endif

用 header 檔的檔名(大寫(xiě)的)代替上面的 FILENAME_H,用底線 代替檔名中的點(diǎn)沉桌。有些人喜歡在 #endif 加上注釋來(lái)提醒他們這個(gè) #endif 指的是什么谢鹊。例如:

#endif /* #ifndef FILENAME_H */

我個(gè)人沒(méi)有這個(gè)習(xí)慣算吩,因?yàn)檫@其實(shí)是很明顯的。當(dāng)然這只是各人的 風(fēng)格不同佃扼,無(wú)傷大雅偎巢。

你只需要在那些有編譯錯(cuò)誤的 header 檔中加入這個(gè)技巧,但在所 有的 header 檔中都加入也沒(méi)什么損失兼耀,到底這是個(gè)好習(xí)慣压昼。

1.5 重新編譯一個(gè)多文件項(xiàng)目

清楚的區(qū)別編譯和連接是很重要的。編譯器使用源碼文件來(lái)產(chǎn)生某種 形式的目標(biāo)文件(object files)瘤运。在這個(gè)過(guò)程中窍霞,外部的符號(hào)參考并 沒(méi)有被解釋或替換。然后我們使用連接器來(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ù)處理 程序,編譯器牵咙,組合程序派近,連接器)。具體的哪些元件被呼叫取決于 輸入文件的類型和你傳遞給它的開(kāi)關(guān)洁桌。

一般來(lái)說(shuō)渴丸,如果你只給它C源碼文件,它將預(yù)處理另凌,編譯谱轨,組合所有 的文件,然后把所得的目標(biāo)文件連接成一個(gè)可執(zhí)行文件(一般生成的 文件被命名為 a.out )吠谢。你當(dāng)然可以這么做土童,但這會(huì)破壞很多我們 把一個(gè)項(xiàng)目分解成多個(gè)文件所得到的好處。

如果你給它一個(gè) -c 開(kāi)關(guān)工坊,gcc 只把給它的文件編譯成目標(biāo)文件献汗, 用源碼文件的文件名命名但把其后綴由“.c”或“.cc”變成“.o”敢订。 如果你給它的是一列目標(biāo)文件, gcc 會(huì)把它們連接成可執(zhí)行文件罢吃, 缺省文件名是 a.out 楚午。你可以改變?nèi)笔∶瞄_(kāi)關(guān) -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)在才開(kāi)始見(jiàn)真章。您學(xué)到點(diǎn)兒東西沒(méi)窟却?)

  1. 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ī)則,如下:

: ...
(tab)<command>
(tab)<command>
.
.
.

例如届氢,考慮以下的 makefile :

=== makefile 開(kāi)始 ===
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 從最上面開(kāi)始,把上 面第一個(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ī)則忧换,它就開(kāi)始檢查磁碟 上這些依靠文件的時(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 中所寫(xiě)的規(guī)則是正確無(wú)誤的,只 列出那些在源碼文件中被 #include 的 header 檔……

2.2 編寫(xiě) make 規(guī)則 (Rules)

最明顯的(也是最簡(jiǎn)單的)編寫(xiě)規(guī)則的方法是一個(gè)一個(gè)的查 看源碼文件涤垫,把它們的目標(biāo)文件做為目的姑尺,而C源碼文件和被它 #include 的 header 檔做為依靠文件。但是你也要把其它被這些 header 檔 #include 的 header 檔也列為依靠文件蝠猬,還有那些被 包括的文件所包括的文件……然后你會(huì)發(fā)現(xiàn)要對(duì)越來(lái)越多的文件 進(jìn)行管理蹂风,然后你的頭發(fā)開(kāi)始脫落铝穷,你的脾氣開(kāi)始變壞,你的臉 色變成菜色社搅,你走在路上開(kāi)始跟電線桿子碰撞,終于你搗毀你的 電腦顯示器项乒,停止編程。到低有沒(méi)有些容易點(diǎn)兒的方法呢?

當(dāng)然有什黑!向編譯器要!在編譯每一個(gè)源碼文件的時(shí)候堪夭,它實(shí)在應(yīng) 該知道應(yīng)該包括什么樣的 header 檔愕把。使用 gcc 的時(shí)候,用 -M 開(kāi)關(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ì)含有命令部分凡涩;你可以自己寫(xiě)入你的命令 或者什么也不寫(xiě)棒搜,而讓 make 使用它的隱含的規(guī)則(參考下面的 2.4 節(jié))。

2.3 Makefile 變量

上面提到 makefiles 里主要包含一些規(guī)則活箕。它們包含的其它的東 西是變量定義力麸。

makefile 里的變量就像一個(gè)環(huán)境變量(environment variable)。 事實(shí)上育韩,環(huán)境變量在 make 過(guò)程中被解釋成 make 的變量克蚂。這些 變量是大小寫(xiě)敏感的,一般使用大寫(xiě)字母筋讨。它們可以從幾乎任何 地方被引用埃叭,也可以被用來(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è)變量神年,你只要在一行的開(kāi)始寫(xiě)下這個(gè)變量的名字已维,后 面跟一個(gè) = 號(hào),后面跟你要設(shè)定的這個(gè)變量的值已日。以后你要引用 這個(gè)變量垛耳,寫(xiě)一個(gè) $ 符號(hào),后面是圍在括號(hào)里的變量名飘千。比如在 下面堂鲜,我們把前面的 makefile 利用變量重寫(xiě)一遍:

=== makefile 開(kāi)始 ===
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 寫(xiě)成:

=== makefile 開(kāi)始 ===
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)你沒(méi)有給出某些命令的時(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 開(kāi)關(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 開(kāi)始處輸入:

all : exec1 exec2

其中 exec1 和 exec2 是我們做為目的的兩個(gè)可執(zhí)行文件。 make 把這個(gè) 'all' 做為它的主要目的叠艳,每次執(zhí)行時(shí)都會(huì)嘗試把 'all' 更新奶陈。但既然這行規(guī)則里沒(méi)有哪個(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

前提是沒(méi)有其它的規(guī)則依靠這個(gè) 'veryclean' 目的,它將永遠(yuǎn) 不會(huì)被執(zhí)行。但是揉稚,如果你明確的使用命令 'make veryclean' 秒啦, make 會(huì)把這個(gè)目的做為它的主要目標(biāo),執(zhí)行那些 rm 命令搀玖。

如果你的磁碟上存在一個(gè)叫 veryclean 文件余境,會(huì)發(fā)生什么事?這 時(shí)因?yàn)樵谶@個(gè)規(guī)則里沒(méi)有任何依靠文件灌诅,所以這個(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)跟開(kāi)括號(hào)蚪燕,函數(shù)名娶牌,空格后跟一列由逗號(hào)分隔的參數(shù),最后 用關(guān)括號(hào)結(jié)束馆纳。例如诗良,在 GNU Make 里有一個(gè)叫 'wildcard' 的函 數(shù),它有一個(gè)參數(shù)鲁驶,功能是展開(kāi)成一列所有符合由其參數(shù)描述的文 件名鉴裹,文件間以空格間隔。你可以像下面所示使用這個(gè)命令:

SOURCES = $(wildcard *.c)

這行會(huì)產(chǎn)生一個(gè)所有以 '.c' 結(jié)尾的文件的列表钥弯,然后存入變量 SOURCES 里径荔。當(dāng)然你不需要一定要把結(jié)果存入一個(gè)變量。

另一個(gè)有用的函數(shù)是 patsubst ( patten substitude, 匹配替 換的縮寫(xiě))函數(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 開(kāi)關(guān))。現(xiàn)在我們要讓 make 把這些規(guī)則當(dāng)做 makefile 檔 的一部分爸邢。這里使用的技巧很像 C 語(yǔ)言中的 #include 系統(tǒng)——我 們要求 make 把這個(gè)文件 include 到 makefile 里樊卓,如下:

include depends

GNU Make 看到這個(gè),檢查 'depends' 目的是否更新了杠河,如果沒(méi)有碌尔, 它用我們給它的命令重新產(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 開(kāi)始 ===

######################################
# 
# 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. 
# 沒(méi)保險(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 的命令行覆蓋它們限次,你愿意就沒(méi)問(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 命令目錄下有沒(méi)有 rm 命令卖漫,如果沒(méi)有费尽,我們使用
# del 命令來(lái)代替,但有可能給我們 'File not found' 這個(gè)錯(cuò)誤信息羊始,這沒(méi)
# 什么大礙旱幼。如果你不是用 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.
#
# 從這里開(kāi)始缘缚,你應(yīng)該不需要改動(dòng)任何東西勾笆。(我是不太相信,太NB了G疟酢)

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ù)和變量都展開(kāi)了酸舍。如果使用 = 的話,函數(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 兜挨,如果沒(méi)有 else 的話)的一段碼加進(jìn) makefile 里;如果不同眯分,把 else 到 endif 間的一段碼加入 makefile (如果有 else )拌汇。 ifneq 的用法剛好相反。

'filter-out' 函數(shù)使用兩個(gè)用空格分開(kāi)的列表弊决,它把第二列表中所 有的存在于第一列表中的項(xiàng)目刪除噪舀。我用它來(lái)處理 DEPS 列表,把所 有已經(jīng)存在的項(xiàng)目都刪除飘诗,而只保留缺少的那些与倡。

我前面說(shuō)過(guò), CPPFLAGS 存有用于隱含規(guī)則中傳給預(yù)處理器的一些 旗標(biāo)昆稿。而 -MD 開(kāi)關(guān)類似 -M 開(kāi)關(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ì)用戶是有意義的志电。

我還沒(méi)有發(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é)
~~~~~~~~~~~~~~~

我希望這篇文章足夠詳細(xì)的解釋了多文件項(xiàng)目是怎么運(yùn)作的,也說(shuō)明了 怎樣安全而合理的使用它婉弹。到此睬魂,你應(yīng)該可以輕松的利用 GNU Make 工 具來(lái)管理小型的項(xiàng)目,如果你完全理解了后面幾個(gè)部分的話镀赌,這些對(duì)于 你來(lái)說(shuō)應(yīng)該沒(méi)什么困難氯哮。

GNU Make 是一件強(qiáng)大的工具,雖然它主要是用來(lái)建立程序商佛,它還有很多 別的用處喉钢。如果想要知道更多有關(guān)這個(gè)工具的知識(shí)姆打,它的句法,函數(shù)肠虽, 和許多別的特點(diǎn)幔戏,你應(yīng)該參看它的參考文件 (info pages, 別的 GNU 工具也一樣,看它們的 info pages. )税课。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末闲延,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子韩玩,更是在濱河造成了極大的恐慌垒玲,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件找颓,死亡現(xiàn)場(chǎng)離奇詭異合愈,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)击狮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)佛析,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人帘不,你說(shuō)我怎么就攤上這事⊙罴” “怎么了寞焙?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)互婿。 經(jīng)常有香客問(wèn)我捣郊,道長(zhǎng),這世上最難降的妖魔是什么慈参? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任呛牲,我火速辦了婚禮,結(jié)果婚禮上驮配,老公的妹妹穿的比我還像新娘娘扩。我一直安慰自己,他們只是感情好壮锻,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布琐旁。 她就那樣靜靜地躺著,像睡著了一般猜绣。 火紅的嫁衣襯著肌膚如雪灰殴。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天掰邢,我揣著相機(jī)與錄音牺陶,去河邊找鬼伟阔。 笑死,一個(gè)胖子當(dāng)著我的面吹牛掰伸,可吹牛的內(nèi)容都是我干的皱炉。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼碱工,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼娃承!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起怕篷,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤历筝,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后廊谓,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體梳猪,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年蒸痹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了春弥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡叠荠,死狀恐怖匿沛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情榛鼎,我是刑警寧澤逃呼,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站者娱,受9級(jí)特大地震影響抡笼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜黄鳍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一推姻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧框沟,春花似錦藏古、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至灾前,卻和暖如春防症,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工蔫敲, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留饲嗽,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓奈嘿,卻偏偏與公主長(zhǎng)得像貌虾,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子裙犹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容