【轉載】爲何不常見include .c文件

備:對於#include  <filename.h> ,編譯器從標準庫路徑開始搜索 filename.h
       對於#include  「filename.h」 ,編譯器從用戶的工做路徑開始搜索 filename.hshell

今天有人問我: #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了的源文件排除出項目定義文件。翻譯

相關文章
相關標籤/搜索