第一章:概述
1.1:make概述
在linux环境下使用make工具能够比较容易的构建一个属于自己的工程,整个工程的编译只需要一个命令就可以完成编译、连接以至于最后的执行。不过我们需要投入一些时间去学习如何完成makefile文件的编写,这个文件也是make正常工作的基础。
所要完成的makefile文件描述了整个工程的编译、连接等规则。其中包括:工程中的哪些源文件需要编译以及如何编译、需要创建那些库文件以及如何创建这些库文件、如何最后产生我们想要的可执行文件。
make是一个命令工具,他解释了makefile中的指令。在makefile文件中描述了整个工程所有文件的编译顺序、编译规则。makefile有自己的书写格式,关键字、函数。想c语言有自己的格式、关键字和函数一样。而且在makefile中可以使用系统shell所提供的任何命令来完成想要的工作。
1.2:准备知识
在讨论make之前首先需要明确一些基本概念:
编译:把高级语言书写的代码转换为机器可识别的机器指令。编译后产生的机器指令虽然可被机器识别,但是还不能被执行。编译时,编译器检察高级语言的语法、函数与变量的声明是否正确。只有所有语法正确、相关变量定义正确,编译器就可以编译出中间目标文件。通常一个高级语言的源文件都可对应一个目标文件。目标文件在linux中默认后缀为“.o”。
链接:将多个目标文件,或者目标文件和库文件链接称为可被操作系统执行的可执行程序。链接器不检查函数所在的源文件,只检查所有目标文件的定义的符号。将目标文件使用的函数和其他目标或者库文件中的相关符号进行合并,并对所有文件中的符号进行重新安排,并连接系统相关文件最终生成可执行程序。
静态库:又称文档文件,十多个目标文件的集合。
共享库:也是多个目标文件的集合,但是这些目标文件按照一种特殊的方式生成。模块中各个成员的地址都是相对地址。使用此共享库的程序在运行时,共享库被动态加载到内存并和主程序在内存中进行链接。多个可执行程序可共享库文件中的代码段。
第二章:GUN make介绍
makemakefile告诉make以何种方式编译和链接程序,当某一文件更新时,make通过比较对应文件的最后修改时间,来决定那些文件需要更新,对需要的文件执行相应命令。
2.1:makefile简介:
当使用make工具进行编译时,工程中一些集中文件在执行make时将会被编译:
1、所有源文件中没有被编译过,则对各个c源文件进行编译并进行链接,生成最后的可执行程序;
2、每一个在上次执行make之后修改过的c源代码文件在本次执行make时将会被重新编译;
3、头文件在上一次执行make之后被修改。则所有包含此头文件的c源文件在本次执行make时将会被重新编译。
2.2:makefile规则介绍:
一个简单的makefile描述规则组成如下:
TARGET... : PREREQUITES
COMMAND
target:规则的目标,通常是最后需要生成的文件名或者为了实现这个目标而必须的中间过程文件名。可以是目标文件、也可以是最后的可执行程序的文件名。另外目标也可以使一个make执行的动作的名称,如clean,我们称这样的目标是伪目标。
prerequisites:规则的依赖。生成规则所需要的文件名列表。通常一个文件依赖于一个或者多个文件。
command:规则的命令行。是规则所要执行的动作(任意的shell命令或者是可在shell下执行的程序)。它限定了make执行这条规则时所需要的动作。
一个规则可以有多个命令行,每一条命令占一行。注意:每一个命令行必须一[tab]字符开始,[tab]字符告诉make此行是一个命令行。make按照命令完成相应的动作。这也是书写makefile中容易产生,而且比较隐蔽的错误。
命令就是任意一个目标的依赖文件发生变化后重建目标的动作描述。一个目标可以没有依赖而只有动作。
在makefile中的规则就是描述在什么情况下、如何重建规则的目标文件,通常规则中包括了目标的依赖关系和重建目标的命令。make执行重建目标的命令,来创建或者重建目标。规则包含了文件之间的依赖关系和更新此规则目标所需要的命令。
make程序根据规则的依赖关系,决定是够执行规则所定义的命令的过程我们称之为执行规则。
2.3:简单的示例
main.c:
#include"hello.h" int main(){ print(); return 0; }
hello.c:
#include"hello.h" void print(){ printf("hello world! "); }
hello.h:
#ifndef HELLO_H #define HELLO_H #include<stdio.h> void print(); #endif
makefile文件如下:
obj = main.o
hello.o main: $(obj) gcc $(obj) -o main main.o: main.c hello.h gcc -c main.c -o main.o hello.o: hello.c hello.h gcc -c hello.c -o hello.o clean: rm -rf *.o main
首先书写时,可以将一个较长行使用反斜杠分解为多行,使得更容易理解。但是需要注意:反斜杠之后不能有空格。在完成这个makefile以后;需要创建可执行程序main,所要做的就是在包含此makefile的目录下键入命令make。删除此目录下的使用make生成的文件,也只需要键入命令make clean就可以了。
命令行必须以tab键开始,以和makefile其他行区别。就是说所有的命令行必须以tab字符开始,但并不是所有的以tab字符开始的行都作为命令行来处理。(记住:make程序本身并不关心命令是如何工作的,对目标文件的更新需要你在规则描述中提供正确的命令。make程序所做的就是当目标程序需要更新时执行规则所定义的命令)。
目标clean不是一个文件,它仅仅代表执行一个动作的标识。正常情况下,不需要执行这个规则所定义的动作,因此目标clean没有出现在其他任何规则的依赖列表中。因此在执行make时,它所指定的动作不会被执行。除非在执行make时明确地指定它。而且目标clean没有任何依赖文件,他只有一个目的,就是通过这个目标名来执行它所定义的命令。makefile中把那些没有任何依赖只有执行动作的目标称为伪目标。
2.4:make如何工作
默认情况下,make执行的是makefile中的第一个规则,此规则的第一个目标称为最终目的或者终极目标。例如上例中的main文件。
当在shell中键入make命令后,make读取当前目录下的makefile文件,并将makefile文件中的第一个目标作为其执行的终极目标,开始处理第一个规则。在处理此规则所定义的命令之前,首先处理目标main所有的依赖文件的更新规则。这些目标文件为目标的规则的处理有下列三种情况:
1、目标文件不存在,使用其描述规则创建它;
2、目标文件存在,目标文件所以来的.c源文件或者头文件中的任何一个比目标文件更新。则根据规则重新编译生成它;
3、目标文件存在,目标文件比它的任何一个依赖文件更新,什么也不做。
在makefile中的一个规则的目标不是终极目标所依赖的,那么这个规则不会被执行,除非明确指定执行这个规则。
完成了对目标文件的创建或者更新之后,make程序将处理终极目标main所在的规则,分为以下三种情况:
1、目标文件不存在,则执行规则以创建目标main;
2、目标文件main存在,其依赖文件中有一个或者多个文件比它更新,则根据规则重新链接生成mian;
3、目标文件存在。它比它的任何一个依赖文件都新,则什么都不做。
总结对一个makefile文件,make首先解析终极目标所在的规则,根据其依赖文件依次寻找创建这些以来文件的规则。首先为第一个依赖文件寻找创建规则,如果第一个依赖文件依赖于其他文件,则同样为这个依赖文件寻找创建规则,知道为所有依赖文件找到合适的创建规则。之后make从最后一个规则回退开始执行,最终完成终极目标的第一个依赖文件的创建和更新。之后对第一个、第二个、第三个、。。。终极目标的依赖文件执行同样的过程。最后一步是创建此规则的目标。
更新终极目标的过程中,如果任何一个规则出现错误make就立即报错并退出。整个过程make只是负责执行规则,而对具体规则所描述的依赖关系的正确性、规则所定义的命令的正确性不做任何判断。就是说,一个规则的依赖关系是否正确、描述重建目标的规则命令行是够正确,make不做任何错误检查。因此编写一个正确的makefile文件爱你就显得尤为重要。
2.5:制定变量
上面的例子中的obj就是一个指定变量,下面每次使用时都可以直接使用;这样做不但减少书写的工作量,而且还可以减少修改而产生错误的可能。
2.6:自动推导规则
在使用make编译.c源文件时,编译.c源文件时,编译.c源文件规则的命令可以不用明确给出。这是因为make本身存在一个默认的规则,能够自动完成对.c文件的编译并生成对应的目标文件。他执行命令cc -c来编译.c文件。在makefile中我们只需要给出需要重建的目标文件名,meke会自动为这个目标文件寻找合适的依赖文件,并且使用正确的命令,来重建这个目标文件,对于上面的例子,此默认规则就是使用命令“cc -c main.c -o main.o”来创建文件'main.o'对一个目标文件。此默认规则称为make的隐含规则。
在书写时我们就可以省略掉描述重名的目标文件与依赖文件的规则,只需要给出那些特定的规则描述。因此上面的例子就可以更加简单的写成:
obj = main.o hello.o main: $(obj) cc -o main $(obj) main.o: hello.h hello.o: hello.h clean: rm -rf *.o main
2.7:另类风格的makefile
我们也可以根据依赖而不是目标对规则进行分组。上例的makefile就可以这样来实现:
obj = main.o hello.o main : $(obj) cc -o main $(obj) #(obj) : hello.h clean: rm -rf *.o main
例子中hello.h作为所有.o文件的依赖文件。但这种书写方式不建议,后期维护会比较麻烦。
书写规则建议的方式是:单目标,多依赖。就是说尽量做到一个规则中只存在一个目标文件,可以有多个依赖文件。尽量避免多目标,单依赖的方式。
2.8:清除工作目录过程文件
规则除了完成源代码编译之外,也可以完成其他任务,例如前面提到的清除编译过程中产生的临时文件的规则。
clean: rm -rf *.o main
在实际应用时,我们把这个规则写成如下稍微复杂一些的样子,以防止始料未及的情况。
.PHONY:clean clean: -rm -rf *.o main
这两个实现有两点不同:
1、通过“.PHONY”特殊目标将clean目标声明为伪目标。避免当磁盘上存在一个名为clean文件时,目标clean所在规则的命令无法执行。
2、在命令行之前使用-,意思是忽略命令“rm”的执行错误。
这样一个目标在makefile中,不能将其作为终极目标。因为我们的初衷并不是当你在命令行上输入make以后执行删除动作。而是要创建或者更新程序。