背景
在 Linux下的软件编译,有时候不得不自己写 makefile 。会不会写 makefile,从一个侧面说明了一个人是否具备完成大型工程的能力。
makefile 关系到了整个工程的编译规则。一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile 定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile 就像一个 Shell 脚本一样,其中也可以执行操作系统的命令。
makefile 带来的好处就是 ——“自动化编译”,一旦写好,只需要一个 make 命令,整个工程就会完全自动编译,极大的提高了软件开发的效率。
make 是一个命令工具,是一个解释 makefile 中指令的命令工具,一般来说,大多数的 IDE 都有这个命令。make 是一条命令,makefile是一个文件,两者搭配使用,完成项目自动化构建。
关于编译和链接
一般来说,无论是C还是C++都要先将源文件编译成中间文件,在windows下是.obj文件,在Linux下是.o文件(Object File),这个过程叫编译(compile)。然后将大量的Object File文件合成可执行文件,这个过程叫链接(link)。
简单来说,源文件首先会生成中间文件,再由中间文件生成可执行文件。编译时,编译器只检查程序语法和函数、变量是否被声明。若函数或变量没有声明,编译器会给出一个警告,但还会生成Object File。在链接时,主要是链接函数和全局变量,链接器会在所有Object File中寻找函数实现,若未找到函数实现,会报出链接错误。
Makefile书写规则
targets : prerequisites
command
…
或是这样:
targets : prerequisites ; command
command
…
target:prerequisites
gcc $^ -o $@
.PHONY:clean
clean:
rm target
target表示目标,以空格分开,可以使用通配符(“*”,“?”和“[…]”)。一般来说,我们的目标基本上是一个文 件,但也有可能是多个文件。
prerequisites表示目标所依赖文件,如果其中的某个文件要比目标文件要新,那么,目标就被认为是“过时的”,被认为是需要重新生成的。
command表示命令行,如果其不与
target:prerequisites
在一行,那么,必须以[Tab键]开头, 如果和 prerequisites 在一行,那么可以用分号做为分隔。.PHONY表示clean定义为伪目标。
如果命令太长,你可以使用反斜框(\)作为换行符。make 对一行上有多少个字符没有限制。
make命令依赖性
输入make命令后,会隐含进行以下工作:
make 会在当前目录下找名字叫“Makefile”或“makefile”的文件。
如果找到,它会找文件中的第一个目标文件(target)。
如果目标文件不存在,或目标文件的依赖文件修改时间要比这个目标文件新,那么,他就会执行后面所定义的命令来生成这个目标文件。
如果目标文件的依赖文件也存在,那么make会在当前文件中找目标文件的依赖性, 如果找到则再根据那一个规则生成目标文件。(类似于堆栈的过程)
这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不处理。make只管文件的依赖性,即如果在我找了依赖关系之后,冒号后面的文件还是不在,那么make就不工作。
Makefile内涵
Makefile里主要包含了以下五个方面:
显示规则。显示规则说明了如何生成一个或多个目标文件。这是由Makefile的书写者指出。
隐晦规则。由于make有自动推导功能,隐晦规则可使Makefile书写简单化。
变量的定义。在Makefile中需要定义一系列的变量,变量一般都是字符串,有点类似于C语言中的宏,当Makefile被执行时,其中的变量会被扩展到相应的引用位置上。
文件指示。其中包括三个部分。一是在一个Makefile中引用另一个Makefile,就像C语言的include一样;另一个是指根据某些情况指定Makefile中有效部分,就像C语言中的#if一样;还有定义一个多行命令。
注释。Makefile中只有行注释,和Linux中的Shell脚本一样,其注释用“#”字符表示,就像C/C++中的“\”一样。
make的工作方式
GNU 的make工作时的执行步骤入下:
读入所有的 Makefile。
读入被 include 的其它 Makefile。
初始化文件中的变量。
推导隐晦规则,并分析所有规则。
为所有的目标文件创建依赖关系链。
根据依赖关系,决定哪些目标要重新生成。
执行生成命令。
Makefile实例
Add.h
#pragma once
int Add(int a, int b);
Add.c
#include "Add.h"
int Add(int a, int b){
return a + b;
}
main.c
#include "Add.h"
#include <stdio.h>
int main(){
int ret = Add(2, 3);
printf("ret=%d\n",ret);
printf("hello world!\n);
return 0;
}