linux C項目make:不能更新 的 一個原因

這個問題在昨天工作中遇到,先記一下自己的探索的結果。

大致問題如下:程序有3個文件組成,如下顯示(工作項目當然要保密的嘛,所以另外自己寫了個最簡單的小程序來說明問題)

  1. /* fun.h */
  2. #ifndef FUN_H
  3. #define FUN_H
  4. #include <stdio.h>
  5. void fun(int f);
  6. #endif

  7. /* fun.c */
  8. #include "fun.h"
  9. void f(int f){ printf("f is %d/n", f);}

  10. /* main.c */
  11. #include "fun.h"
  12. int main()
  13. {
  14.     f(1);
  15.     return 0;
  16. }
然後,那個Makefile文件是這麼寫的(最簡單的形式的主幹部分,對於clean爲目標就不寫了。)

  1. main:
  2. <TAB>gcc -o main main.c 
那個<TAB>是TAB鍵的意思,好了make可以得到main執行文件,可是後來需要把main.c文件文件中的f(1)改成f(4),然後再make一遍的時候,屏幕上顯示了類似於“沒有東西更新”提示。當時沒有留意(慘了,居然沒有留意)。就直接運行了,結果還是輸出1;過了十分鐘,在同事的提醒下才發現這個問題,誒。

發現問題時候,開始時直接用make clean; make來重新編譯鏈接得到可執行文件;後來我覺得好像以前或多或少的也發現了這種類似的問題,所以就看看是什麼原因導致了沒有發現更新。

首先,解釋一下make程序的原理。make程序在運行時,找當前目錄的makefile文件或者是Makefile文件(默認),makefile文件的主幹部分是像上面這種格式。

目標1:依賴文件集合1
<TAB>命令1
目標2:依賴文件集合2
<TAB>命令2
...

其中的第一個目標就是make程序生成的最終目標。這個就像一棵樹(說嚴格一點,不是樹,應該是有向無環圖DAG),其中葉子節點就是這些.c, .h等已經有的資料,最終目標就是樹根。舉例:比如上面那個例子,寫嚴格的makefile文件是這個樣子:

main:main.o fun.o
<TAB>gcc -o main main.o fun.o
main.o:main.c fun.h
<TAB>gcc -c main.c
fun.o:fun.c fun.h
<TAB>gcc -c fun.c

嗯,這個就形成了一顆依賴樹結構,這個:前面的是目標,就是說main的目標依賴於main.o和fun.o文件,而main.o文件又依賴於main.c和fun.h文件;同理,fun.o文件依賴於fun.h和fun.c文件。

那麼,當這個程序第一次運行make的時候,

(1)、make程序會根據makefile文件得到上面的依賴關係樹;
(2)、從當前目錄找main文件(結果是找不到)
(3)、然後,就找main.o和fun.o文件(找來幹嘛,當然是要生成main文件了),也沒有找到
(4)、然後再找main.o文件所依賴的main.c和fun.h文件(這次找到了),然後用命令來生成main.o文件
(5)、同樣方法生成fun.o文件
(6)、用生成的main.o,fun.o文件生成最終目標的main

那麼,生成了main程序文件之後,對main.c文件進行修改,make程序又是如何編譯的呢?還是按照以上的6步編譯嘛?不可能,如果這樣的話,那麼一個幾百個文件的大項目,豈不是要等等等...

揭曉的結果是:make程序是根據文件的 修改日期 和 依賴關係 判斷那些模塊需要跟新的。具體步驟如下:

(1)、make程序根據makefile文件得到依賴關係樹
(2)、從當前目錄中找到了main文件,從屬性中得到該文件的最後修改日期。
(3)、再得到main文件的依賴文件main.o和fun.o文件的日期,如果main.o沒有找到,或者main.o文件的最後修改日期 比 main文件的要 新 ! 那麼就說明main目標已經過期,要執行命令生成main文件;fun.o文件也類似
(4)、用(3)類似的方法判斷下去,一路判斷;

如此一來,程序發現main.c的日期比main.o的新,所以運行命令重新生成main.o,再由main.o和fun.o的文件生成新的main文件。(發現沒有:遞歸在這方面的優勢,我估計make程序用遞歸寫這部分,但我沒看過,也不清楚,哪位大俠指點一下小子,感激不盡。)

好了,說明了make運行的機制,現在說一下那個問題的結症:就是第一次寫的那個makefile文件沒有寫依賴關係!!

  1. main:
  2. <TAB>gcc -o main main.c
main文件沒有依賴關係時候,make程序就無法知道從什麼地方去進行判斷,也就是說main程序一經生成,以後的make的結果都是“最新”的,除非把main刪掉(用make clean)。

再做個試驗,如果在上面那個詳細的makefile文件中,把下面依賴關係的fun.h刪掉

main.o:main.c fun.h
<TAB>gcc -c main.c
fun.o:fun.c fun.h
<TAB>gcc -c fun.c


再修改fun.h文件裏面的內容(加個空格啊什麼都可以),保存退出;然後make,看看結果是什麼...


好了,那麼文件的依賴關係到底怎麼找呢?其實gcc最常見的命令有兩種,一種是編譯,一種是鏈接

編譯命令:gcc -c file.c 結果是生成file.o; 鏈接命令是:gcc -o file file1.o file2.o ...結果是生成file可執行文件

那麼對於編譯命令來說 (gcc -c file.c )目標文件file.o的依賴文件肯定有file.c存在還有file.c所依賴的一些頭文件(在file.c中#include 的那些文件,以及那些頭文件中又包含的頭文件...)。

在gcc中由一個命令可以找出一個.c文件所依賴的(包括嵌套的)所有頭文件(原理是找文件中的#include,取出裏面的內容)

gcc -MM file.c

嗯,很好,那我們的makefile文件可以這樣做,

首先,gcc -MM main.c > main.d
gcc -MM fun.c > fun.d

這樣就可以生成依賴關係文件main.c fun.d

然後,makefile文件大概是這個樣子:(具體的內容以前看過,但忘記了。)

main.o:main.d
    gcc -c main.c
...

原理大概是這個樣子的,makefile具體的寫法,google上去吧。呵呵(那裏的資料很多很詳細)


最後說一下的是,開始時別爲了省事就把這些依賴關係不寫全部,甚至不寫;如果這樣,到以後的維護過程要麼是發現不了改的東西,要麼就只好make clean重新編譯哦。

這些原理也可以說明爲什麼c++中那麼喜歡用“前向聲明"的技術,爲了減少編譯時間(因爲這樣減少了文件的依賴關係了哦),可以google相關資料哇。

說明:其實那些依賴關係說是樹並不恰當,比如上面的情況中,那個fun.o,main.o都依賴fun.h也就是說並不是一對多的關係,是多對多的關係,所以是圖,那個依賴關係是單向的,畫畫圖看看就知道,應該是有向無環圖(DAG)(不應該有環的,如果有環,那就是a依賴b,b依賴c,c依賴a...無窮潰也)

上面的過程和結論,有很大部分是我根據網上make原理的資料猜想出來的,不一定正確,如果各位不吝賜教,在下感激涕零啊...
發佈了54 篇原創文章 · 獲贊 16 · 訪問量 34萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章