>>> 此貼的回復 >> 網上有一個《跟我一起寫Makefile》很不錯,你可以看一下
>>> 此貼的回復 >> 無論是在Linux還是在Unix環境中,make都是一個非常重要的編譯命令。不管是自己進行項目開發還是安裝應用軟件,我們都經常要用到make或make install。利用make工具,我們可以將大型的開發項目分解成為多個更易於管理的模塊,對於一個包括幾百個源文件的應用程序,使用make和makefile工具就可以簡潔明快地理順各個源文件之間紛繁復雜的相互關系。而且如此多的源文件,如果每次都要鍵入gcc命令進行編譯的話,那對程序員來說簡直就是一場災難。而make工具則可自動完成編譯工作,並且可以只對程序員在上次編譯後修改過的部分進行編譯。因此,有效的利用make和makefile工具可以大大提高項目開發的效率。同時掌握make和makefile之後,您也不會再面對著Linux下的應用軟件手足無措了。 但令人遺憾的是,在許多講述Linux應用的書籍上都沒有詳細介紹這個功能強大但又非常復雜的編譯工具。在這裡我就向大家詳細介紹一下make及其描述文件makefile。 Makefile文件 Make工具最主要也是最基本的功能就是通過makefile文件來描述源程序之間的相互關系並自動維護編譯工作。而makefile 文件需要按照某種語法進行編寫,文件中需要說明如何編譯各個源文件並連接生成可執行文件,並要求定義源文件之間的依賴關系。makefile 文件是許多編譯器--包括 Windows NT 下的編譯器--維護編譯信息的常用方法,只是在集成開發環境中,用戶通過友好的界面修改 makefile 文件而已。 在 UNIX 系統中,習慣使用 Makefile 作為 makfile 文件。如果要使用其他文件作為 makefile,則可利用類似下面的 make 命令選項指定 makefile 文件: $ make -f Makefile.debug 例如,一個名為prog的程序由三個C源文件filea.c、fileb.c和filec.c以及庫文件LS編譯生成,這三個文件還分別包含自己的頭文件a.h 、b.h和c.h。通常情況下,C編譯器將會輸出三個目標文件filea.o、fileb.o和filec.o。假設filea.c和fileb.c都要聲明用到一個名為defs的文件,但filec.c不用。即在filea.c和fileb.c裡都有這樣的聲明: #include "defs" 那麼下面的文檔就描述了這些文件之間的相互聯系:
>>> 此貼的回復 >> --------------------------------------------------------- #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 ---------------------------------------------------------- 這個描述文檔就是一個簡單的makefile文件。 從上面的例子注意到,第一個字符為 # 的行為注釋行。第一個非注釋行指定prog由三個目標文件filea.o、fileb.o和filec.o鏈接生成。第三行描述了如何從prog所依賴的文件建立可執行文件。接下來的4、6、8行分別指定三個目標文件,以及它們所依賴的.c和.h文件以及defs文件。而5、7、9行則指定了如何從目標所依賴的文件建立目標。 當filea.c或a.h文件在編譯之後又被修改,則 make 工具可自動重新編譯filea.o,如果在前後兩次編譯之間,filea.C 和a.h 均沒有被修改,而且 test.o 還存在的話,就沒有必要重新編譯。這種依賴關系在多源文件的程序編譯中尤其重要。通過這種依賴關系的定義,make 工具可避免許多不必要的編譯工作。當然,利用 Shell 腳本也可以達到自動編譯的效果,但是,Shell 腳本將全部編譯任何源文件,包括哪些不必要重新編譯的源文件,而 make 工具則可根據目標上一次編譯的時間和目標所依賴的源文件的更新時間而自動判斷應當編譯哪個源文件。 Makefile文件作為一種描述文檔一般需要包含以下內容: ◆ 宏定義 ◆ 源文件之間的相互依賴關系 ◆ 可執行的命令 Makefile中允許使用簡單的宏指代源文件及其相關編譯信息,在Linux中也稱宏為變量。在引用宏時只需在變量前加$符號,但值得注意的是,如果變量名的長度超過一個字符,在引用時就必須加圓括號()。 下面都是有效的宏引用: $(CFLAGS) $2 $Z $(Z) 其中最後兩個引用是完全一致的。 需要注意的是一些宏的預定義變量,在Unix系統中,$*、$@、$?和$ Make命本身可帶有四種參數:標志、宏定義、描述文件名和目標文件名。其標准形式為: Make [flags] [macro definitions] [targets] Unix系統下標志位flags選項及其含義為: -f file 指定file文件為描述文件,如果file參數為"-"符,那麼描述文件指向標准輸入。如果沒有"-f"參數,則系統將默認當前目錄下名為makefile或者名為Makefile的文件為描述文件。在Linux中, GNU make 工具在當前工作目錄中按照GNUmakefile、makefile、Makefile的順序搜索 makefile文件。 -i 忽略命令執行返回的出錯信息。 -s 沉默模式,在執行之前不輸出相應的命令行信息。 -r 禁止使用build-in規則。 -n 非執行模式,輸出所有執行命令,但並不執行。 -t 更新目標文件。 -q make操作將根據目標文件是否已經更新返回"0"或非"0"的狀態信息。 -p 輸出所有宏定義和目標文件描述。 -d Debug模式,輸出有關文件和檢測時間的詳細信息。 Linux下make標志位的常用選項與Unix系統中稍有不同,下面我們只列出了不同部分: -c dir 在讀取 makefile 之前改變到指定的目錄dir。 -I dir 當包含其他 makefile文件時,利用該選項指定搜索目錄。 -h help文擋,顯示所有的make選項。 -w 在處理 makefile 之前和之後,都顯示工作目錄。 通過命令行參數中的target ,可指定make要編譯的目標,並且允許同時定義編譯多個目標,操作時按照從左向右的順序依次編譯target選項中指定的目標文件。如果命令行中沒有指定目標,則系統默認target指向描述文件中第一個目標文件。 通常,makefile 中還定義有 clean 目標,可用來清除編譯過程中的中間文件,例如: clean: rm -f *.o 運行 make clean 時,將執行 rm -f *.o 命令,最終刪除所有編譯過程中產生的所有中間文件。 隱含規則 在make 工具中包含有一些內置的或隱含的規則,這些規則定義了如何從不同的依賴文件建立特定類型的目標。Unix系統通常支持一種基於文件擴展名即文件名後綴的隱含規則。這種後綴規則定義了如何將一個具有特定文件名後綴的文件(例如.c文件),轉換成為具有另一種文件名後綴的文件(例如.o文件): .c:.o $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $< 系統中默認的常用文件擴展名及其含義為: .o 目標文件 .c C源文件 .f FORTRAN源文件 .s 匯編源文件 .y Yacc-C源語法 .l Lex源語法 在早期的Unix系統系統中還支持Yacc-C源語法和Lex源語法。在編譯過程中,系統會首先在makefile文件中尋找與目標文件相關的.C文件,如果還有與之相依賴的.y和.l文件,則首先將其轉換為.c文件後再編譯生成相應的.o文件;如果沒有與目標相關的.c文件而只有相關的.y文件,則系統將直接編譯.y文件。 而GNU make 除了支持後綴規則外還支持另一種類型的隱含規則--模式規則。這種規則更加通用,因為可以利用模式規則定義更加復雜的依賴性規則。模式規則看起來非常類似於正則規則,但在目標名稱的前面多了一個 % 號,同時可用來定義目標和依賴文件之間的關系,例如下面的模式規則定義了如何將任意一個 file.c 文件轉換為 file.o 文件: %.c:%.o $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $< #EXAMPLE# 下面將給出一個較為全面的示例來對makefile文件和make命令的執行進行進一步的說明,其中make命令不僅涉及到了C源文件還包括了Yacc語法。本例選自"Unix Programmer's Manual 7th Edition, Volume 2A" Page 283-284 下面是描述文件的具體內容:
#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) ---------------------------------------------------------- 通常在描述文件中應象上面一樣定義要求輸出將要執行的命令。在執行了make命令之後,輸出結果為: $ 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
最後的數字信息是執行"@size make"命令的輸出結果。之所以只有輸出結果而沒有相應的命令行,是因為"@size make"命令以"@"起始,這個符號禁止打印輸出它所在的命令行。 描述文件中的最後幾條命令行在維護編譯信息方面非常有用。其中"print"命令行的作用是打印輸出在執行過上次"make print"命令後所有改動過的文件名稱。系統使用一個名為print的0字節文件來確定執行print命令的具體時間,而宏$?則指向那些在print文件改動過之後進行修改的文件的文件名。如果想要指定執行print命令後,將輸出結果送入某個指定的文件,那麼就可修改P的宏定義: make print "P= cat>zap" 在Linux中大多數軟件提供的是源代碼,而不是現成的可執行文件,這就要求用戶根據自己系統的實際情況和自身的需要來配置、編譯源程序後,軟件才能使用。只有掌握了make工具,才能讓我們真正享受到到Linux這個自由軟件世界的帶給我們無窮樂趣。
GNU make 指南 譯者按: 本文是一篇介紹 GNU Make 的文章,讀完後讀者應該基本掌握了 make 的用法。而 make 是所有想在 Unix (當然也包括 Linux )系統上編程的用戶必須掌握的工具。如果你寫的程序中沒有用到 make ,則說明你寫的程序只是個人的練習程序,不具有任何實用的價值。也許這麼說有點 兒偏激,但 make 實在是應該用在任何稍具規模的程序中的。希望本文可以為中國的 Unix 編程初學者提供一點兒有用的資料。中國的 Linux 用戶除了學會安裝紅帽子以外, 實在應該嘗試寫一些有用的程序。個人想法,大家參考。
-------------------------------------------------------------------------------- C-Scene 題目 #2 多文件項目和 GNU Make 工具 作者: 喬治富特 (Goerge Foot) 電子郵件: [email protected] Occupation: Student at Merton College, Oxford University, England 職業:學生,默爾頓學院,牛津城大學,英格蘭 IRC匿名: gfoot
-------------------------------------------------------------------------------- 拒絕承諾:作者對於任何因此而對任何事物造成的所有損害(你所擁有或不 擁有的實際的,抽象的,或者虛擬的)。所有的損壞都是你自己的責任,而 與我無關。
所有權: “多文件項目”部分屬於作者的財產,版權歸喬治富特1997年 五月至七月。其它部分屬 CScene 財產,版權 CScene 1997年,保留所有 版權。本 CScene 文章的分發,部分或全部,應依照所有其它 CScene 的文章 的條件來處理。
0) 介紹 ~~~~~~~~~~~~~~~ 本文將首先介紹為什麼要將你的C源代碼分離成幾個合理的獨立檔案,什麼時 候需要分,怎麼才能分的好。然後將會告訴你 GNU Make 怎樣使你的編譯和連 接步驟自動化。對於其它 Make 工具的用戶來說,雖然在用其它類似工具時要 做適當的調整,本文的內容仍然是非常有用的。如果對你自己的編程工具有懷 疑,可以實際的試一試,但請先閱讀用戶手冊。
1) 多文件項目 ~~~~~~~~~~~~~~~~~~~~~~
1.1為什麼使用它們?
首先,多文件項目的好處在那裡呢? 它們看起來把事情弄的復雜無比。又要 header 文件,又要 extern 聲明,而且如果需要查找一個文件,你要在更多的文件裡搜索。
但其實我們有很有力的理由支持我們把一個項目分解成小塊。當你改 動一行代碼,編譯器需要全部重新編譯來生成一個新的可執行文件。 但如果你的項目是分開在幾個小文件裡,當你改動其中一個文件的時 候,別的源文件的目標文件(object files)已經存在,所以沒有什麼 原因去重新編譯它們。你所需要做的只是重現編譯被改動過的那個文 件,然後重新連接所有的目標文件罷了。在大型的項目中,這意味著 從很長的(幾分鐘到幾小時)重新編譯縮短為十幾,二十幾秒的簡單 調整。
只要通過基本的規劃,將一個項目分解成多個小文件可使你更加容易 的找到一段代碼。很簡單,你根據代碼的作用把你的代碼分解到不同 的文件裡。當你要看一段代碼時,你可以准確的知道在那個文件中去 尋找它。
從很多目標文件生成一個程序包 (Library)比從一個單一的大目標文件 生成要好的多。當然實際上這是否真是一個優勢則是由你所用的系統 來決定的。但是當使用 gcc/ld (一個 GNU C 編譯/連接器) 把一個程 序包連接到一個程序時,在連接的過程中,它會嘗試不去連接沒有使 用到的部分。但它每次只能從程序包中把一個完整的目標文件排除在 外。因此如果你參考一個程序包中某一個目標檔中任何一個符號的話, 那麼這個目標文件整個都會被連接進來。要是一個程序包被非常充分 的分解了的話,那麼經連接後,得到的可執行文件會比從一個大目標 文件組成的程序包連接得到的文件小得多。
又因為你的程序是很模塊化的,文件之間的共享部分被減到最少,那 就有很多好處——可以很容易的追蹤到臭蟲,這些模塊經常是可以用 在其它的項目裡的,同時別人也可以更容易的理解你的一段代碼是干 什麼的。當然此外還有許多別的好處……
GNU make 指南 譯者按: 本文是一篇介紹 GNU Make 的文章,讀完後讀者應該基本掌握了 make 的用法。而 make 是所有想在 Unix (當然也包括 Linux )系統上編程的用戶必須掌握的工具。如果你寫的程序中沒有用到 make ,則說明你寫的程序只是個人的練習程序,不具有任何實用的價值。也許這麼說有點 兒偏激,但 make 實在是應該用在任何稍具規模的程序中的。希望本文可以為中國的 Unix 編程初學者提供一點兒有用的資料。中國的 Linux 用戶除了學會安裝紅帽子以外, 實在應該嘗試寫一些有用的程序。個人想法,大家參考。
-------------------------------------------------------------------------------- C-Scene 題目 #2 多文件項目和 GNU Make 工具 作者: 喬治富特 (Goerge Foot) 電子郵件: [email protected] Occupation: Student at Merton College, Oxford University, England 職業:學生,默爾頓學院,牛津城大學,英格蘭 IRC匿名: gfoot
-------------------------------------------------------------------------------- 拒絕承諾:作者對於任何因此而對任何事物造成的所有損害(你所擁有或不 擁有的實際的,抽象的,或者虛擬的)。所有的損壞都是你自己的責任,而 與我無關。
所有權: “多文件項目”部分屬於作者的財產,版權歸喬治富特1997年 五月至七月。其它部分屬 CScene 財產,版權 CScene 1997年,保留所有 版權。本 CScene 文章的分發,部分或全部,應依照所有其它 CScene 的文章 的條件來處理。
0) 介紹 ~~~~~~~~~~~~~~~ 本文將首先介紹為什麼要將你的C源代碼分離成幾個合理的獨立檔案,什麼時 候需要分,怎麼才能分的好。然後將會告訴你 GNU Make 怎樣使你的編譯和連 接步驟自動化。對於其它 Make 工具的用戶來說,雖然在用其它類似工具時要 做適當的調整,本文的內容仍然是非常有用的。如果對你自己的編程工具有懷 疑,可以實際的試一試,但請先閱讀用戶手冊。
1) 多文件項目 ~~~~~~~~~~~~~~~~~~~~~~
1.1為什麼使用它們?
首先,多文件項目的好處在那裡呢? 它們看起來把事情弄的復雜無比。又要 header 文件,又要 extern 聲明,而且如果需要查找一個文件,你要在更多的文件裡搜索。
但其實我們有很有力的理由支持我們把一個項目分解成小塊。當你改 動一行代碼,編譯器需要全部重新編譯來生成一個新的可執行文件。 但如果你的項目是分開在幾個小文件裡,當你改動其中一個文件的時 候,別的源文件的目標文件(object files)已經存在,所以沒有什麼 原因去重新編譯它們。你所需要做的只是重現編譯被改動過的那個文 件,然後重新連接所有的目標文件罷了。在大型的項目中,這意味著 從很長的(幾分鐘到幾小時)重新編譯縮短為十幾,二十幾秒的簡單 調整。
只要通過基本的規劃,將一個項目分解成多個小文件可使你更加容易 的找到一段代碼。很簡單,你根據代碼的作用把你的代碼分解到不同 的文件裡。當你要看一段代碼時,你可以准確的知道在那個文件中去 尋找它。
從很多目標文件生成一個程序包 (Library)比從一個單一的大目標文件 生成要好的多。當然實際上這是否真是一個優勢則是由你所用的系統 來決定的。但是當使用 gcc/ld (一個 GNU C 編譯/連接器) 把一個程 序包連接到一個程序時,在連接的過程中,它會嘗試不去連接沒有使 用到的部分。但它每次只能從程序包中把一個完整的目標文件排除在 外。因此如果你參考一個程序包中某一個目標檔中任何一個符號的話, 那麼這個目標文件整個都會被連接進來。要是一個程序包被非常充分 的分解了的話,那麼經連接後,得到的可執行文件會比從一個大目標 文件組成的程序包連接得到的文件小得多。
又因為你的程序是很模塊化的,文件之間的共享部分被減到最少,那 就有很多好處——可以很容易的追蹤到臭蟲,這些模塊經常是可以用 在其它的項目裡的,同時別人也可以更容易的理解你的一段代碼是干 什麼的。當然此外還有許多別的好處……
_____________________________
1.2 何時分解你的項目
很明顯,把任何東西都分解是不合理的。象“世界,你們好”這樣的 簡單程序根本就不能分,因為實在也沒什麼可分的。把用於測試用的 小程序分解也是沒什麼意思的。但一般來說,當分解項目有助於布局、 發展和易讀性的時候,我都會采取它。在大多數的情況下,這都是適 用的。(所謂“世界,你們好”,既 'hello world' ,只是一個介 紹一種編程語言時慣用的范例程序,它會在屏幕上顯示一行 'hello world' 。是最簡單的程序。)
如果你需要開發一個相當大的項目,在開始前,應該考慮一下你將 如何實現它,並且生成幾個文件(用適當的名字)來放你的代碼。 當然,在你的項目開發的過程中,你可以建立新的文件,但如果你 這麼做的話,說明你可能改變了當初的想法,你應該想想是否需要 對整體結構也進行相應的調整。
對於中型的項目,你當然也可以采用上述技巧,但你也可以就那麼開 始輸入你的代碼,當你的碼多到難以管理的時候再把它們分解成不同 的檔案。但以我的經驗來說,開始時在腦子裡形成一個大概的方案, 並且盡量遵從它,或在開發過程中,隨著程序的需要而修改,會使開 發變得更加容易。
1.3 怎樣分解項目
先說明,這完全是我個人的意見,你可以(也許你真的會?)用別的 方式來做。這會觸動到有關編碼風格的問題,而大家從來就沒有停止 過在這個問題上的爭論。在這裡我只是給出我自己喜歡的做法(同時 也給出這麼做的原因):
i) 不要用一個 header 文件指向多個源碼文件(例外:程序包 的 header 文件)。用一個 header定義一個源碼文件的方式 會更有效,也更容易查尋。否則改變一個源文件的結構(並且 它的 header 文件)就必須重新編譯好幾個文件。
ii) 如果可以的話,完全可以用超過一個的 header 文件來指向同 一個源碼文件。有時將不可公開調用的函數原型,類型定義 等等,從它們的C源碼文件中分離出來是非常有用的。使用一 個 header 文件裝公開符號,用另一個裝私人符號意味著如果 你改變了這個源碼文件的內部結構,你可以只是重新編譯它而 不需要重新編譯那些使用它的公開 header 文件的其它的源文 件。
iii) 不要在多個 header 文件中重復定義信息。 如果需要, 在其中一個 header 文件裡 #include 另一個,但 是不要重復輸入相同的 header 信息兩次。原因是如果你以後改 變了這個信息,你只需要把它改變一次,不用搜索並改變另外一 個重復的信息。
iv) 在每一個源碼文件裡, #include 那些聲明了源碼文件中的符 號的所有 header 文件。這樣一來,你在源碼文件和 header 文件對某些函數做出的矛盾聲明可以比較容易的被編譯器發現。
1.4 對於常見錯誤的注釋
a) 定義符 (Identifier) 在源碼文件中的矛盾:在C裡,變量和函數的缺 省狀態是公用的。因此,任何C源碼檔案都可以引用存在於其它源 碼檔中的通用 (global) 函數和通用變量,既使這個檔案沒有那個變 量或函數的聲明或原型。因此你必須保證在不同的兩個檔案裡不能 用同一個符號名稱,否則會有連接錯誤或者在編譯時會有警告。
一種避免這種錯誤的方法是在公用的符號前加上跟其所在源文件有 關的前綴。比如:所有在 gfx.c 裡的函數都加上前綴“gfx_”。如果 你很小心的分解你的程序,使用有意義的函數名稱,並且不是過分 使用通用變量,當然這根本就不是問題。
要防止一個符號在它被定義的源文件以外被看到,可在它的定義前 加上關鍵字“static”。這對只在一個檔案內部使用,其它檔案都 都不會用到的簡單函數是很有用的。
b) 多次定義的符號: header 檔會被逐字的替換到你源文件裡 #include 的位置的。因此,如果 header 檔被 #include 到一個以上的源文件 裡,這個 header 檔中所有的定義就會出現在每一個有關的源碼文件 裡。這會使它們裡的符號被定義一次以上,從而出現連接錯誤(見 上)。
解決方法: 不要在 header 檔裡定義變量。你只需要在 header 檔裡聲明它們然後在適當的C源碼文件(應該 #include 那個 header 檔的那個)裡定義它們(一次)。對於初學者來說,定義和聲明是 很容易混淆的。聲明的作用是告訴編譯器其所聲明的符號應該存在, 並且要有所指定的類型。但是,它並不會使編譯器分配貯存空間。 而定義的做用是要求編譯器分配貯存空間。當做一個聲明而不是做 定義的時候,在聲明前放一個關鍵字“extern”。
例如,我們有一個叫“counter”的變量,如果想讓它成為公用的, 我們在一個源碼程序(只在一個裡面)的開始定義它:“int counter;”,再在相關的 header 檔裡聲明它:“extern int counter;”。
函數原型裡隱含著 extern 的意思,所以不需顧慮這個問題。
c) 重復定義,重復聲明,矛盾類型: 請考慮如果在一個C源碼文件中 #include 兩個檔 a.h 和 b.h, 而 a.h 又 #include 了 b.h 檔(原因是 b.h 檔定義了一些 a.h 需要的類型),會發生什麼事呢?這時該C源碼文件 #include 了 b.h 兩次。因此每一個在 b.h 中的 #define 都發生了兩次,每一 個聲明發生了兩次,等等。理論上,因為它們是完全一樣的拷貝, 所以應該不會有什麼問題,但在實際應用上,這是不符合C的語法 的,可能在編譯時出現錯誤,或至少是警告。
解決的方法是要確定每一個 header 檔在任一個源碼文件中只被包 含了一次。我們一般是用預處理器來達到這個目的的。當我們進入 每一個 header 檔時,我們為這個 header 檔 #define 一個巨集 指令。只有在這個巨集指令沒有被定義的前提下,我們才真正使用 該 header 檔的主體。在實際應用上,我們只要簡單的把下面一段 碼放在每一個 header 檔的開始部分:
#ifndef FILENAME_H #define FILENAME_H
然後把下面一行碼放在最後:
#endif
用 header 檔的檔名(大寫的)代替上面的 FILENAME_H,用底線 代替檔名中的點。有些人喜歡在 #endif 加上注釋來提醒他們這個 #endif 指的是什麼。例如:
#endif /* #ifndef FILENAME_H */
我個人沒有這個習慣,因為這其實是很明顯的。當然這只是各人的 風格不同,無傷大雅。
你只需要在那些有編譯錯誤的 header 檔中加入這個技巧,但在所 有的 header 檔中都加入也沒什麼損失,到底這是個好習慣。
1.5 重新編譯一個多文件項目
清楚的區別編譯和連接是很重要的。編譯器使用源碼文件來產生某種 形式的目標文件(object files)。在這個過程中,外部的符號參考並 沒有被解釋或替換。然後我們使用連接器來連接這些目標文件和一些 標准的程序包再加你指定的程序包,最後連接生成一個可執行程序。 在這個階段,一個目標文件中對別的文件中的符號的參考被解釋,並 報告不能被解釋的參考,一般是以錯誤信息的形式報告出來。
基本的步驟就應該是,把你的源碼文件一個一個的編譯成目標文件的格 式,最後把所有的目標文件加上需要的程序包連接成一個可執行文件。 具體怎麼做是由你的編譯器決定的。這裡我只給出 gcc (GNU C 編譯 器)的有關命令,這些有可能對你的非 gcc 編譯器也適用。
gcc 是一個多目標的工具。它在需要的時候呼叫其它的元件(預處理 程序,編譯器,組合程序,連接器)。具體的哪些元件被呼叫取決於 輸入文件的類型和你傳遞給它的開關。
一般來說,如果你只給它C源碼文件,它將預處理,編譯,組合所有 的文件,然後把所得的目標文件連接成一個可執行文件(一般生成的 文件被命名為 a.out )。你當然可以這麼做,但這會破壞很多我們 把一個項目分解成多個文件所得到的好處。
如果你給它一個 -c 開關,gcc 只把給它的文件編譯成目標文件, 用源碼文件的文件名命名但把其後綴由“.c”或“.cc”變成“.o”。 如果你給它的是一列目標文件, gcc 會把它們連接成可執行文件, 缺省文件名是 a.out 。你可以改變缺省名,用開關 -o 後跟你指定 的文件名。
因此,當你改變了一個源碼文件後,你需要重新編譯它: 'gcc -c filename.c' 然後重新連接你的項目: 'gcc -o exec_filename *.o'。 如果你改變了一個 header 檔,你需要重新編譯所有 #include 過 這個檔的源碼文件,你可以用 'gcc -c file1.c file2.c file3.c' 然後象上邊一樣連接。
當然這麼做是很繁瑣的,幸虧我們有些工具使這個步驟變得簡單。 本文的第二部分就是介紹其中的一件工具:GNU Make 工具。
(好家伙,現在才開始見真章。您學到點兒東西沒?)
2) GNU Make 工具 ~~~~~~~~~~~~~~~~
2.1 基本 makefile 結構
GNU Make 的主要工作是讀進一個文本文件, makefile 。這個文 件裡主要是有關哪些文件(‘target’目的文件)是從哪些別的 文件(‘dependencies’依靠文件)中產生的,用什麼命令來進行 這個產生過程。有了這些信息, make 會檢查磁碟上的文件,如果 目的文件的時間戳(該文件生成或被改動時的時間)比至少它的一 個依靠文件舊的話, make 就執行相應的命令,以便更新目的文件。 (目的文件不一定是最後的可執行檔,它可以是任何一個文件。)
makefile 一般被叫做“makefile”或“Makefile”。當然你可以 在 make 的命令行指定別的文件名。如果你不特別指定,它會尋 找“makefile”或“Makefile”,因此使用這兩個名字是最簡單 的。
一個 makefile 主要含有一系列的規則,如下:
: ... (tab) (tab) . . .
例如,考慮以下的 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 結束 ===