首页 > 系统 > Linux > 正文

linux下makefile的妙用

2024-06-28 16:04:18
字体:
来源:转载
供稿:网友

一:起因

(0)首先我们假设读者已经熟悉了linux 下的vim的常用操作(如,u 撤消 或  :undo   CTRL-R 重做 或  :redo);

(1)Linux下面的make命令,应用与makefile文件,当我们需要对一个C / C++ 或者java等一个工程里面的所有源文件进行编译时,每一次编译,都不想重复操作以前的命令,更不想把未更改过的源文件再次进行不必要的编译操作 ——最佳选择makefile(PS:虽然这时,你可以选择写一个shell,但是shell需要太多的代码了,好多检测和判断需要自己来写)

(2)make命令执行时,需要一个 Makefile 文件,以告诉make命令需要怎么样的去编译和链接程序。

Makefile的书写规则是:1)如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。2)如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。3)如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的C文件,并链接目标程序。只要我们的Makefile写得够好,所有的这一切,我们只用一个make命令就可以完成,make命令会自动智能地根据当前的文件修改的情况来确定哪些文件需要重编译,从而自己编译所需要的文件和链接目标程序。

二:详解

(1)首先简介一下Linux GCC常用命令

     1)简单编译(一步到位)

[cpp] view plain copy//hello.c  #include <stdio.h>  int main(void)  {       PRintf("Hello World!/n");       return 0;  }  

这个程序,一步到位的编译指令是:gcc hello.c -o hello

    2)实质上,上述编译过程是分为四个阶段进行的,即预处理(也称预编译,Preprocessing)、编译(Compilation)、汇编 (Assembly)和连接(Linking)(如下)

2.1预处理

gcc -E hello.c -o hello.i 或 gcc -E hello.c 可以输出hello.i文件中存放着hello.c经预处理之后的代码。打开hello.i文件,看一看,就明白了。后面那条指令,是直接在命令行窗口中输出预处理后的代码.gcc的-E选项,可以让编译器在预处理后停止,并输出预处理结果。在本例中,预处理结果就是将stdio.h 文件中的内容插入到hello.c中了。

2.2编译为汇编代码(Compilation)

预处理之后,可直接对生成的hello.i文件编译,生成汇编代码:

gcc -S hello.i -o hello.s

gcc的-S选项,表示在程序编译期间,在生成汇编代码后,停止,-o输出汇编代码文件。

2.3汇编(Assembly)

对于上一小节中生成的汇编代码文件hello.s,gas汇编器负责将其编译为目标文件,如下:

gcc -c hello.s -o hello.o

2.4连接(Linking)

gcc连接器是gas提供的,负责将程序的目标文件与所需的所有附加的目标文件连接起来,最终生成可执行文件。附加的目标文件包括静态连接库和动态连接库。对于上一小节中生成的hello.o,将其与C标准输入输出库进行连接,最终生成程序hellogcc hello.o -o hello在命令行窗口中,执行./hello, 让它说HelloWorld吧!

    3)多个程序文件的编译

通常整个程序是由多个源文件组成的,相应地也就形成了多个编译单元,使用GCC能够很好地管理这些编译单元。假设有一个由hello1.c和 hello2.c两个源文件组成的程序,为了对它们进行编译,并最终生成可执行程序hello,可以使用下面这条命令:gcc hello1.c hello2.c -o hello如果同时处理的文件不止一个,GCC仍然会按照预处理、编译和链接的过程依次进行。如果深究起来,上面这条命令大致相当于依次执行如下三条命令:

[cpp] view plain copygcc -c hello1.c -o hello1.o  gcc -c hello2.c -o hello2.o  gcc hello1.o hello2.o -o hello  

(2)makefile的书写

0)Makefile的一般格式。

[cpp] view plain copytarget ... : prerequisites ...  command  ...  ...  其中,target可以是一个目标文件、Object File或可执行文件;还可以是一个标签(Label)如“伪目标” —— command产生的结果

prerequisites 就是,要生成那个target所需要的文件或是目标 ——— 产生target所需要的依赖文件或目录

command 是make需要执行的命令。(任意的Shell命令)可以看出,target 这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中;

prerequisites中如果,有一个以上的文件比target文件要新的话,command所定义的命令就会被执行---这就是Makefile的规则----也就是Makefile中最核心的内容。

1)  假设我有a.c,b.c, main.c三个源文件,最终要编译成一个名为main的可执行文件

如果我想清除掉我编译后的文件,可以rm命令,当然我们也可以清除的功能写入到makefile里边。makefile提供了一个伪目标的功能,可以为你的makefile提供额外的功能。下面是提供了clean功能的makefile文件中的代码:

[plain] view plain copymain : a.o b.o main.o             @gcc a.o b.o main.o -o main             @echo ok             @gcc -c main.c    b.o : b.c             @gcc -c b.c    a.o : a.c            @gcc -c a.c    # 用".PHONY {目标名}"定义一个伪目标  # 用"make {目标名}"执行该伪目标  .PHONY : clean  clean :                @rm -f main *.o                @echo clean  

2)解释

makefile中,#代表注释,每一段命令之后至少要有一个空行;具体的命令通过tab键来表示 .PHONY : clean 用来定义伪目标

执行make clean即可进入clean的功能

此外,makefile提供了系统默认的自动化变量$^:代表所有依赖文件$@:代表目标$<:代表第一个依赖文件所以我们可以用gcc $^ -o $@代替刚刚makefile中的gcc a.o b.o main.o -o main

3)makefile的高级应用:

在makefile中使用变量,就是一个字符串,可以理解成C语言中的宏;比如,我们声明一个变量,叫objects, OBJECTS, objs, OBJS, obj, 或是 OBJ,反正不管什么啦,只要能

够表示obj文件就行了。我们在makefile一开始就这样定义:

objects = main.o kbd.o command.o display.o /insert.o search.o files.o utils.o

于是,我们就可以很方便地在makefile中以“$(objects)”的方式来使用这个变量了

三:实例举证

(1)如果一个工程有3个头文件,和8个C文件;为了完成(一)中的那三个规则,Makefile应该是:

[cpp] view plain copyedit : main.o kbd.o command.o display.o /  insert.o search.o files.o utils.o      gcc -o edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o    main.o : main.c defs.h      gcc -c main.c    kbd.o : kbd.c defs.h command.h      gcc -c kbd.c    command.o : command.c defs.h command.h      gcc -c command.c    display.o : display.c defs.h buffer.h      gcc -c display.c    insert.o : insert.c defs.h buffer.h      gcc -c insert.c    search.o : search.c defs.h buffer.h      gcc -c search.c    files.o : files.c defs.h buffer.h command.h      gcc -c files.c    utils.o : utils.c defs.h      gcc -c utils.c    clean :      rm edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o  反斜杠(/)是换行符的意思;文件名为“Makefile”或“makefile”,然后在该目录下直接输入命令“make”,就可以生成执行文件edit;如果要删除执行文件和所有的中间目标文件,那么,只要简单地执行一下“make clean”就可以了。

在这个makefile中,目标文件(target)包含:执行文件edit和中间目标文件(*.o),依赖文件(prerequisites)就是冒号后面的那些 .c 文件和 .h文件。每一个 .o 文件都有一组依赖文件,而这些 .o 文件又是执行文件 edit 的依赖文件。依赖关系的实质上就是说明了目标文件是由哪些文件生成的,换言之,目标文件是哪些文件更新的。

在定义好依赖关系后,后续的那一行定义了如何生成目标文件的操作系统命令,一定要以一个Tab键作为开头。记住,make并不管命令是怎么工作的,他只管执行所定义的命令。make会比较targets文件和prerequisites文件的修改日期,如果prerequisites文件的日期要比targets文件的日期要新,或者target不存在的话,那么,make就会执行后续定义的命令。

这里要说明一点的是,clean不是一个文件,它只不过是一个动作名字,有点像C语言中的lable一样,其冒号后什么也没有,那么,make就不会自动去找文件的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令,就要在make命令后明显得指出这个lable的名字。这样的方法非常有用,我们可以在一个makefile中定义不用的编译或是和编译无关的命令,比如程序的打包,程序的备份,等等。

(2)在默认的方式下,也就是我们只输入make命令:1、make会在当前目录下找名字叫“Makefile”或“makefile”的文件。

2、如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“edit”这个文件,并把这个文件作为最终的目标文件。

3、如果edit文件不存在,或是edit所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新,那么,他就会执行后面所定义的命令来生成edit这个文件。

4、如果edit所依赖的.o文件也不存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。(这有点像一个堆栈的过程)

5、当然,你的C文件和H文件是存在的啦,于是make会生成 .o 文件,然后再用 .o 文件生命make的终极任务,也就是执行文件edit了。

这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。通过上述分析,我们知道,像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过,我们可以显示要make执行。即命令——“make clean”,以此来清除所有的目标文件,以便重编译。

于是在我们编程中,如果这个工程已被编译过了,当我们修改了其中一个源文件,比如file.c,那么根据我们的依赖性,我们的目标file.o会被重编译(也就是在这个依性关系后面所定义的命令),于是file.o的文件也是最新的啦,于是file.o的文件修改时间要比edit要新,所以edit也会被重新链接了(详见edit目标文件后定义的命令)。而如果我们改变了“command.h”,那么,kdb.o、command.o和files.o都会被重编译,并且,edit会被重链接。

(3)使用变量更改后:

[cpp] view plain copyobjects = main.o kbd.o command.o display.o /  insert.o search.o files.o utils.o    edit : $(objects)      gcc -o edit $(objects)    main.o : main.c defs.h      gcc -c main.c    kbd.o : kbd.c defs.h command.h      gcc -c kbd.c    command.o : command.c defs.h command.h      gcc -c command.c    display.o : display.c defs.h buffer.h      gcc -c display.c    insert.o : insert.c defs.h buffer.h      gcc -c insert.c    search.o : search.c defs.h buffer.h      gcc -c search.c    files.o : files.c defs.h buffer.h command.h      gcc -c files.c    utils.o : utils.c defs.h      gcc -c utils.c    .PHONY : clean  clean :      -rm edit $(objects)  
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表