備:對於#include <filename.h> ,編譯器從標准庫路徑開始搜索 filename.h
對於#include “filename.h” ,編譯器從用戶的工作路徑開始搜索 filename.h
有人問: #include能不能include一個(多個.c文件)?
偶的回答是:從理論上講可以,但是不推薦。
為什么經常見到include .h文件而不是include .c文件?或者說include是不是就是為包含.h文件設定的語法?這個問題的答案偶不知道,沒有見有文檔記載、說明這個問題。不過從語法角度講,include的意思就是從當前位置包含另外一個文件,就象宏替換一樣把當前行用另外一個文件的整個內容替換掉。
從這點講,include .c文件是可行的,c編譯器完全能夠正常處理。但是為什么不常見include .c文件?從設計角度上講,源代碼區分為.h和.c文件,是為了接口與實現的分離,實際上兩者沒什么本質的差別。.h文件提供接口,.c文件提供具體的實現,兩者可以一一對應,也可以不一一對應,沒有強制要求。一個.c文件做為一個模塊的實現,有可能要跟其他的模塊打交道,這個時候就需要include其他模塊的接口(其他模塊的.h文件);而包含其他模塊的實現(.c文件)是沒有意義的、危險的。
所以,我們不應該在項目中include .c文件,這樣使用者出於直覺很難想到這里會有問題,增加了排錯的難度。前幾天偶移植一個國際知名大公司的代碼就遇到了這個問題,耗費了半天的時間查看了全部的源碼和makefile才發現了這個不常見編譯現象。當然,那個公司的代碼之所以這么做,是他認為這些代碼已經很成熟了,不需要修改和反復重新編譯。但它的做法確實對我的調試造成了很大的障礙。
----- GNU Make Document 中的相關章節 -----
4.12 自動生成依賴
在為一個程序編寫的makefile文件中,常常需要寫許多僅僅是說明一些OBJ文件依靠頭文件的規則。例如,如果‘main.c’通過一條#include語句使用‘defs.h’,您需要寫入下的規則:
main.o: defs.h
您需要這條規則讓make知道如果‘defs.h’一旦改變必須重新構造‘main.o’。由此您可以明白對於一個較大的程序您需要在makefile文件中寫很多這樣的規則。而且一旦添加或去掉一條#include語句您必須十分小心地更改makefile文件。
為避免這種煩惱,現代C編譯器根據原程序中的#include語句可以為您編寫這些規則。如果需要使用這種功能,通常可在編譯源程序時加入‘-M’開關,例如,下面的命令:
cc -M main.c
產生如下輸出:
main.o : main.c defs.h
這樣您就不必再親自寫這些規則,編譯器可以為您完成這些工作。
注意,由於在makefile文件中提及構造‘main.o’,因此‘main.o’將永遠不會被隱含規則認為是中間文件而進行搜尋,這同時意味着make不會在使用它之后自動刪除它;參閱隱含規則鏈。
對於舊版的make程序,通過一個請求命令,如‘make depend’,利用編譯器的特點生成依賴是傳統的習慣。這些命令將產生一個‘depend’文件,該文件包含所有自動生成的依賴;然后makefile 文件可以使用include命令將它們讀入(參閱包含其它makefile文件)。
在GNU make中,重新構造makefile文件的特點使這個慣例成為了過時的東西――您永遠不必具體告訴make重新生成依賴,因為GNU make總是重新構造任何過時的makefile文件。參閱Makefile文件的重新生成的過程。
我們推薦使用自動生成依賴的習慣是把makefile文件和源程序文件一一對應起來。如,對每一個源程序文件‘name.c’有一名為‘name.d’的 makefile文件和它對應,該makefile文件中列出了名為‘name.o’的OBJ文件所依賴的文件。這種方式的優點是僅在源程序文件改變的情況下才有必要重新掃描生成新的依賴。
這里有一個根據C語言源程序‘name.c’生成名為‘name.d’依賴文件的格式規則:
%.d: %.c
set -e; $(CC) -M $(CPPFLAGS) $< \
| sed 's/\($*\)\.o[ :]*/\1.o $@ : /g' > $@; \
[ -s $@ ] || rm -f $@
關於定義格式規則的信息參閱定義與重新定義格式規則。‘-e’開關是告訴shell如果$(CC)命令運行失敗(非零狀態退出)立即退出。正常情況下,shell退出時帶有最后一個命令在管道中的狀態(sed),因此make不能注意到編譯器產生的非零狀態。
對於GNU C編譯器您可以使用‘-MM’開關代替‘-M’,這是省略了有關系統頭文件的依賴。詳細內容參閱《GNU CC使用手冊》中控制預處理選項。
命令Sed的作用是翻譯(例如):
main.o : main.c defs.h
到:
main.o main.d : main.c defs.h
這使每一個‘.d’文件和與之對應的‘.o’文件依靠相同的源程序文件和頭文件,據此,Make可以知道如果任一個源程序文件和頭文件發生變化,則必須重新構造依賴文件。
一旦您定義了重新構造‘.d’文件的規則,您可以使用使用include命令直接將它們讀入,(參閱包含其它makefile文件),例如:
sources = foo.c bar.c
include $(sources:.c=.d)
(這個例子中使用一個代替變量參照從源程序文件列表‘foo.c bar.c'翻譯到依賴文件列表‘foo.d bar.d'。詳細內容參閱替換引用。)所以,‘.d’的makefile文件和其它makefile文件一樣,即使沒用您的任何進一步的指令,make 同樣會在必要的時候重新構建它們。參閱Makefile文件的重新生成過程。
GNU Make手冊的開頭就說出了很多人不知道的知識——它不僅僅用於編譯的:p
----- GNU Make Document 中的相關章節 -----
GNU Make符合IEEE Standard 1003.2-1992 (POSIX.2) 6.2章節的規定。
因為C語言程序更具有代表性,所以我們的例子基於C語言程序,但Make並不是僅僅能夠處理C語言程序,它可以處理那些編譯器能夠在Shell命令下運行的的各種語言的程序。事實上,GNU Make不僅僅限於程序,它可以適用於任何如果一些文件變化導致另外一些文件必須更新的任務。
如果要使用Make,必須先寫一個稱為Makefile的文件,該文件描述程序中各個文件之間的相互關系,並且提供每一個文件的更新命令。在一個程序中,可執行程序文件的更新依靠OBJ文件,而OBJ文件是由源文件編譯得來的。
一旦合適的Makefile文件存在,每次更改一些源文件,在shell命令下簡單的鍵入:
make
就能執行所有的必要的重新編譯任務。Make程序根據Makefile文件中的數據和每個文件更改的時間戳決定哪些文件需要更新。對於這些需要更新的文件,Make基於Makefile文件發布命令進行更新,進行更新的方式由提供的命令行參數控制。
這屬於濫用#include.
為何要分為頭文件和源文件?本來就是為了傳遞如下信息:
類型定義
外部函數原型
外部變量
宏
這部分不產生任何實際代碼的東西。
c各文件之間的代碼是通過#include來引入的嗎?這屬於連接器的工作!
懷疑使用這個的人,也許是因為源文件的代碼太長了,就濫用#include,將源代碼分到幾個文件上。
我估計將實現這種招數的辦法是將源代碼划分為一個主多個從的關系,主的引入所有從文件。
他們的內容有如下規定:
主文件,包括所有外部連接的代碼(公共)
從文件,所有元素具備內部連接(私有)
對於客戶端來說,只有主文件才是他們真正去要連接的。
而因為從文件都是內部連接,相關的.obj將毫無疑義的被丟棄,因為主文件有着一模一樣的拷貝。
當然更蹩腳的辦法是將這些#include了的源文件排除出項目定義文件。