linux C项目make:不能更新 的 一个原因

来源:互联网 发布:滚床单 知乎 编辑:程序博客网 时间:2024/05/12 03:50
这个问题在昨天工作中遇到,先记一下自己的探索的结果。

大致问题如下:程序有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原理的资料猜想出来的,不一定正确,如果各位不吝赐教,在下感激涕零啊...
原创粉丝点击