以下内容源于C语言中文网的学习与整理,非原创,如有侵权请告知删除。
一、Makefile文件是什么
Makefile 文件描述了 Linux 系统下 C/C++ 工程文件的编译规则,比如某些文件是否需要编译、文件编译的顺序、文件间的依赖关系、文件是否需要重建等等。
它用来自动化编译 C/C++ 项目。一旦写编写好 Makefile 文件,只需要一个 make 命令,整个工程就开始自动编译,不再需要手动输入一堆源文件与参数,也不需要手动执行 GCC 命令。
以 Linux 下的C语言开发为例。多文件编译生成一个文件,编译的命令如下所示:
gcc -o outfile name1.c name2.c ...
outfile 是要生成的可执行程序的名字,nameN.c 是源文件的名字。当源文件的数量不多时,可以选择这样的编译方式,但如果源文件很多,会遇到下面问题。
(1)编译的时候需要链接库的的问题。
拿C语言来说,编译的时候 gcc 只会默认链接一些基本的C语言标准库,很多源文件依赖的标准库都需要我们手动链接。比如,下面列举了一些需要我们手动链接的标准库:
- name1.c 用到了数学计算库 math 中的函数,我们得手动添加参数 -Im;
- name4.c 用到了小型数据库 SQLite 中的函数,我们得手动添加参数 -lsqlite3;
- name5.c 使用到了线程,我们需要去手动添加参数 -lpthread。
除了标准库,还有其他文件需要链接第三方库。这导致编译命令非常长,而且可能会涉及到文件链接的顺序问题,所以手动编译会很麻烦。如果使用 Makefile 就不一样了,它会彻底简化编译的操作。把要链接的库文件放在 Makefile 中,制定相应的规则和对应的链接顺序。这样只需要执行 make 命令,工程就会自动编译。每次想要编译工程的时候就执行 make ,省略掉手动编译中的参数选项和命令,非常方便。
(2)编译大的工程会花费很长的时间。
如果我们去做项目开发,免不了要去修改工程项目的源文件,每次修改后都要去重新编译。一个大的工程项目可不止有几个的源文件,里面的源文件个数可能有成百上千个,比如一个内核或者是一个软件的源码包。要完巨量文件的编译,消耗的时间很长。
Makefile 支持多线程并发操作,会极大的缩短我们的编译时间。而且编译整个工程的时候,make 命令只会编译我们修改过的文件,没有修改的文件不用重新编译,这也缩短了编译时间。
当然还可能遇到其他问题,比如:工程文件中的源文件的类型很多,需要选择不同的编译器;文件可能会分布在不同的目录中,使用时需要调整路径等等。这些问题都可以通过Makefile 来解决。
我们只需要完整地编写一次Makefile文件,以后只要不增加或者是删除工程中的文件,Makefile 基本上不用去修改。
Makefile这些特征为为我们提供了极大的便利,很大程度上提高编译的效率。
二、Makefile文件的书写规则
1、格式说明
Makefile 描述的是文件编译的相关规则,它的规则主要是两个部分组成:依赖的关系、执行的命令。
其结构如下所示:
targets : prerequisitescommand
或者
targets : prerequisites; commandcommand
说明如下:
- targets是规则的目标。可以是 Object File(一般称它为中间文件),也可以是可执行文件,还可以是一个标签。
- prerequisites是依赖文件,是生成 targets 所需要的文件或者是目标。可以多个,也可以没有。
- command是make时执行的命令(任意的 shell 命令)。一条规则中可以有很多行的命令;每行命令可以有很多条命令,每行中的多条命令之间用分号隔开;每行命令后面不需要添加什么符号表示结束。
- 目标和依赖文件之间要使用冒号分隔开,命令的开始一定要使用
Tab
键。实例:Makefile文件中代码如下
test:test.cgcc -o test test.c
上述代码实现的功能就是编译 test.c 文件。其中 test 是目标文件, test.c 是依赖文件,重建目标文件需要执行的命令是
gcc -o test test.c
。然后我们在 shell 中执行 make 命令,程序就会自动执行,得到最终的目标文件。这就是 Makefile 的基本语法与使用方法。2、Makefile中的内容
Makefile 中的内容,它主要包含有五个部分:
(1)显式规则
显式规则说明如何生成一个或多的的目标文件。由 Makefile 的书写者明显指出要生成的文件、依赖文件和生成命令。
(2)隐晦规则
由于 make 命令有自动推导的功能,我们可以简略地书写 Makefile。
(3)变量的定义
在 Makefile 中定义的变量一般都是字符串,当 Makefile 被执行时,其中的变量都会被扩展到相应的引用位置上。
(4)文件指示
文件指示包括三个部分,一个是在一个 Makefile 中引用另一个 Makefile,就像C语言中的 include 一样;另一个是根据某些情况指定 Makefile 中的有效部分,就像C语言中的预编译 #if 一样;还有就是定义一个多行的命令。
(5)注释
Makefile 中只有行注释,和 UNIX 的 Shell 脚本一样,其注释是用“#”字符,这个就像 C/C++ 中的“//”一样。如果你要在你的 Makefile 中使用“#”字符,可以用反斜框进行转义,如“\#”。
三、Makefile的工作流程
当执行 make 命令时,make 就会到当前文件下寻找将要执行的编译规则,即 Makefile 文件。Makefile文件可以使用的文件名分别是GNUmakefile、makefile 和 Makefile 。一般在工程中都写成Makefile。
make命令也是按照这个顺序去寻找Makefile文件。如果文件不存在,make 就会给我们报错,提示:
make:*** 没有明确目标并且找不到 makefile。停止
1、Makefile 的具体工作流程
通过例子来说明Makefile 的具体工作流程。Makefile文件内容如下:
main:main.o test1.o test2.ogcc main.o test1.o test2.o -o main main.o:main.c test.hgcc -c main.c -o main.o test1.o:test1.c test.hgcc -c test1.c -o test1.o test2.o:test2.c test.hgcc -c test2.c -o test2.o
默认情况下,make 执行的是 Makefile 文件中的第一个规则(Makefile 文件中从上到下第一次出现依赖关系的那个规则。注意,这里说的规则,指依赖关系和命令的组合体),此规则的目标称之为“终极目标”。
这个例子中,Makefile的具体工作流程:在 shell 中执行 make 命令时, make 会读取当前目录下的 Makefile 文件,并将 Makefile 文件中的第一个目标作为其执行的“终极目标”,开始处理第一个规则(终极目标所在的规则)。在上述例子中,第一个规则就是目标 "main" 所在的规则。规则描述了 "main" 的依赖关系,并定义了链接 ".o" 文件以生成目标 "main" 的命令。make 在执行这个规则所定义的命令之前,首先处理目标 "main" 的所有的依赖文件(例子中的那些 ".o" 文件)的更新规则(以这些 ".o" 文件为目标的规则)。对这些 ".o" 文件为目标的规则处理有下列三种情况:
- 目标 ".o" 文件不存在,则使用其描述规则创建它;
- 目标 ".o" 文件存在,但是它所依赖的 ".c" 文件和 ".h" 文件中的任何一个文件比它更(第四声)新(在上一次 make 之后被修改),则根据规则重新编译生成它;
- 目标 ".o" 文件存在,目标 ".o" 文件比它的任何一个依赖文件更新(它的依赖文件在上一次 make 之后没有被修改),则什么也不做。
通过上面的更新规则,我们可以了解到中间文件(即编译时生成的 ".o" 文件)的作用,即检查某个源文件是否进行过修改,判断最终目标文件是否需要重建。我们执行 make 命令时,只有那些修改过的源文件或者是不存在的目标文件才会进行重建,而那些没有改变的文件不用重新编译,这样在很大程度上节省时间,提高编程效率。小的工程项目可能体会不到,项目工程文件越大,效果才越明显。
2、清除工作目录中的过程文件
中间文件会让整个工程看起来很乱,因此在编写 Makefile 文件时,一般会在末尾加上这样的规则语句,来清理编译时的中间文件和生成的最终目标文件,方便我们下次的使用。
.PHONY:clean clean:rm -rf *.o test
“.PHONY”表明clean是一个伪目标。clean不是具体的文件,不会与第一个目标文件产生任何关联,因此执行 make 时不会执行它下面的命令。
这句话解答了我以前不了解Makefile时存在的疑惑:Makefile文件中那么多的目标,想要达成这些目标,在make的时候,是在后面一个个地添加目标吗?还是说只要make第一个目标,就会顺序地带动其他目标?现在看来,默认情况下make时候是第一个目标,其他目标如果是第一个目标的依赖文件,则会先处理其他目标,处理完之后才进行第一个目标的命令执行。当然,我们也可以在make后面直接指定目标,比如这里的make clean。