之前对makefile的编写,限于刚开始接触,我都比较局限一些死板的格式,有时候就会显得有些繁琐。在进一步了解一些系统编译和连接的知识后,对makefile编写流程有了一些新的认识,因此来此梳理梳理,方便更为灵活地编写makefile。函数
限于makefile认识不足,这里参考了一篇比较好博文:makefilespa
makefile带来直接好处就是——“自动化编译”。一旦写好,只须要一个make命令,整个工程彻底自动编译,因此十分方便。而Makefile文件就是告诉make命令怎么样地去编译和连接程序。可是想要比较灵活的运用它,仍是先要熟悉一些关于系统对程序编译和连接的知识。.net
通常来讲,对C、C++程序、先把源文件编译成中间代码文件。Linux下是 .o 文件即 Object File,在Windows下也就是 .obj 文件,这个动做叫作编译(compile)。而后再把大量的.O文件合成执行文件,这个动做叫做连接(link)code
编译时,编译器须要的是语法的正确,函数与变量的声明的正确。对于后者,一般是让咱们告诉编译器头文件的所在位置(头文件中放声明,而定义放在C/C++文件中),只要全部的语法正确,编译器就能够编译出中间目标文件。通常来讲,每一个源文件都应该对应于一个中间目标文件(.O文件或是OBJ文件)。blog
连接时,主要是连接函数和全局变量,因此,咱们可使用这些中间目标文件(.O文件或.OBJ文件)来连接咱们的应用程序。连接器并无论函数所在的源文件,只管函数的中间目标文件。在大多数时候,因为源文件太多,编译生成的中间目标文件太多,而在连接时须要明显地指出中间目标文件名,这对于编译很不方便,因此,咱们要给中间目标文件打个包,在Windows下这种包叫“库文件”(Library File),也就是 .lib 文件,在Linux下,是Archive File,也就是 .a 文件字符串
总的来讲就是,首先源文件-> .o文件,再由.o文件->可执行文件。在编译时,编译器只检测程序语法,和函数、变量是否被声明。若是函数未被声明,编译器会给出一个警告,但能够生成Object File。而在连接程序时,连接器会在全部的.o文件中找寻函数的实现,若是找不到,那到就会报连接错误码(Linker Error) get
来个例子感觉一下,编译器
hello: hello.o hello.o: hello.c gcc -c hello.c -o hello.o
这里make,便会自动编译了。这当中生成可执行文件hello依赖于hello.o,hello.o 依赖于 hello.c; 最后找到了hello.c即可以gcc生成hello.o这样日后‘带’,目标文件的hello便连接上.o文件去执行了。这里值得注意的是写gcc命令时须要添上 -c选项,用来保证获得的.o文件可重连接,否则基本会make报错(某些状况如直接gcc hello.c -o hello例外)。
自动化
直白点说,最后生成的可执行文件就是连接.o文件获得;而.o文件靠着“依赖关系”生成。io
还有注意一点就是在Makefile中的命令(如gcc ..),必需要以[Tab]键开始,否则你极可能就会make出错哦~。
上面例子直接连接一个中间目标文件,显得比较简单,当遇到源文件须要连接多个中间目标文件时会是怎么个样子呢?
好比 分别建立一个加法的add.c 和 add.h ,一个减法 sub.c和 sub.h 最后main.c 来调用add 和 sub实现加减法。此时Makefile 会像这样
main: main.o add.o sub.o main.o: main.c gcc -c main.c -o main.o add.o: add.c gcc -c add.c -o add.o #加-c 指定生成为可重连接.o文件 sub.o: sub.c gcc -c sub.c -o sub.o .PHONY:clean clean: -rm -rf *.o
使用看看
从上面注意几个地方:
①当最终目标文件依赖多个.o时,将依赖的多个.o 一块儿写到main: 后面。而后依次以 目标:依赖文件 gcc... 的格式,罗列全部依赖关系
②因为在上面的过程当中生成了多个中间.o文件(实际工程中确定是比较多的),因此每次编译完成,后续基本还须要进行必定的清理工做,这时候就用上一个 "clean" (后面细说一下)来清理。
③ .PHONY意思表示clean是一个“伪目标”。也便是不管clean是否最新,必定执行它。rm命令前面加了一个小减号的意思就是,也许某些文件出现问题,但并不理睬。固然,clean的规则不要放在文件的开头,不然这就会变成make的默认目标,相信谁也不肯意这样。不成文的规矩是——“clean历来都是放在文件的最后”
关于clean:
它只不过是一个动做名字,有点像c语言中的lable同样,其冒号后什么也没有,那么,make就不会自动去找它的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令(不只用于clean,其余lable一样适用),就要在make命令后明显得指出这个lable的名字。这样的方法很是有用,咱们能够在一个Makefile中定义不用的编译或是和编译无关的命令,好比程序的打包,程序的备份,等等。
到这,大体能够了解了makefile,以及大体怎么实现makefile.好, 那么make又是怎么用makefile进行执行的呢?
一、make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
二、若是找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“main”这个文件,并把这个文件做为最终的目标文件。
三、若是main文件不存在,或是main所依赖的后面的 .o 文件的文件修改时间要比main这个文件新,那么,它就会执行后面所定义的命令来生成main这个文件。
四、若是main所依赖的.o文件也不存在,那么make会在当前文件中找目标为.o文件的依赖性,若是找到则再根据那一个规则生成.o文件。(这有点像一个堆栈的过程)
五、固然,你的C文件和H文件是存在的啦,因而make会生成 .o 文件,而后再用 .o 文件生命make的终极任务,也就是执行文件main了。
这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程当中,若是出现错误,好比最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。make只管文件的依赖性,即若是在我找了依赖关系以后,冒号后面的文件仍是不在,那么对不起,我就不工做啦。
从前面的makefile编写来看, 当中咱们每写一个依赖关系就须要写一个形如gcc X.c -o X.o生成命令,这里还好,如果较大的工程,这样不免就太繁琐了,因此据了解,通常在公司专门编写makefile的人是不会那样写的。还有写着更简洁方式,就是利用下面这几个符号:
$^ 表明全部的依赖文件
$@ 表明全部的目标文件
$< 表明第一个依赖文件
注意$<表明的是依赖关系表中的第一项(若是咱们想引用的是整个关系表,那么就应该使用$^),具体到咱们这里就是%.c。而$@表明的是当前语句的目标,即%.o。因而即可以将上面的makefile改写成
.PHONY:clean main: main.o add.o sub.o main.o: main.c gcc -c $< -o $@ add.o: add.c gcc -c $^ -o $@ sub.o: sub.c gcc -c $^ -o $@ clean: rm -rf *.o
因为连接依赖的是中间目标文件.o ,若是makefile变得复杂,那么咱们就有可能会少写一个依赖关系,获得.o文件不完整,从而致使编译失败。因此,为了makefile的易维护,在makefile中咱们可使用常量(这里看到不少人都把它说成变量,我的认为 它在后面并无被改变,因次叫常量更好)。定义一个常量OBJS来表示全部的.o文件,因而便还可将Makefile写成这样:
.PHONY:clean OBJS = main.o\ //\转义字符 add.o\ sub.o main: $(OBJS) %.o : %.c gcc -c $^ -o $@ clean: -rm -rf $(OBJS)
这样是否是感受又简洁了很多! 这里的%.o : %.c 想必均可以你们均可以猜出来,这表明的意思就是全部的.o文件依赖相应的.C文件,是一个模式规则。这样一来,make命令就会自动将全部的.c源文件编译成同名的.o文件。不用这样便又省去好几步。
到这,相信聪明的你,能够更灵活编写makefile (ヾ(´A`)ノ゚ 打脸,怎能说出如此膨胀的话来)。 无论了,再补充补充关于Makefile的东西 就赶忙溜~~
在应用时候,定下的规则通常这样:
①若是这个工程没有编译过,那么咱们的全部c文件都要编译并被连接。
②若是这个工程的某几个c文件被修改,那么咱们只编译被修改的c文件,并连接目标程序。
③若是这个工程的头文件被改变了,那么咱们须要编译引用了这几个头文件的c文件,并连接目标程序。
因此只要咱们的makefile写得够好,全部的这一切,咱们只用一个make命令就能够完成,make命令会自动智能地根据当前的文件修改的状况来肯定哪些文件须要重编译,从而本身编译所须要的文件和连接目标程序。