在linux下编写C/C++程序,一般会习惯用Makefile来编译程序。总结一下自己学习使用Makefile的经验。
一个C/C++项目,会有多个头文件和源程序文件,有时候还会用多个文件夹来组源代码的结构。文件之间会有依赖关系,当一个被依赖的文件被修改之后,不仅它自己需要重新编译,依赖它的文件也需要重新编译。然而如果一个文件不被其它文件依赖,那么它更改之后,就只需要重新编译自己,然后重新进行链接。当程序的文件比较多的时候,很多时候只有部分文件需要重新编译,不需要整个重新编译,所以这种编译链接的方式,是很有效的。
在linux下面,使用make作为程序自动维护的工具。它会检查程序中文件或者模块的修改情况,但是如果要决定哪些文件需要重新编译,还需要知道文件之间的依赖关系,而这就是Makefile的作用。并且,Makefile还指定编译过程所使用的命令,选项,需要生成的目标等。所以当Makefile写好之后,编译程序只是在命令行敲一个make的事情。(这是比较简单的情况,make也可以有自己的选项,参数等。)
下面来看一个具体的Makefile。
按照我的理解,Makefile一般分为四个部分,第一部分是编译选项,使用哪个编译器,编译部分的命令参数,链接部分的命令参数,这里用到了Name = soemthing的形式,这是Makefile定义宏变量的语句,例如CXX = g++-4.7 , 宏变量定义之后,可以使用$符号来使用变量,后面出现的$(CXX)就表示取变量CXX的值放在那个位置。CXXFLAGS也是一个宏变量,它有多个用空格隔开的值,所以也可以看作是列表,但最后使用它的值的时候,仍然是整个列表的值替换在那里。就像C/C++里面的宏一样。
第二部分是文件依赖关系,这里又可以分为三个小的部分,第一个是指定包含的额外的头文件,使用到的库。第二个是头文件和源文件列表。需要注意的是Makefile的语句以行为单位,所以给HEADERS添加多个文件的时候,可以写在一行,也可以通过续行符"\"写在多行里面。需要注意的是,"\"后面不能有空格,在Vim里面,可以看到,如果是有效的续行符的话,会有颜色高亮。另外,添加的最后一个文件后面不要再加续行符,Makefile的空白行是忽略的,所以如果在最后一个文件后面加上了续行符,例如polyfrac.h后面,那么SOURCES这一行的定义会被遮盖,后面的编译就会出现问题。
第三部分就是OBJECTS那一句,表示OBJECTS是由SOURCES的每一个文件,将.cpp后缀换成.o得到。这个是很好用的功能。
第三部分是自动推导规则。在第二部分定义了OBJECTS由SOURCES对应生成,那么这一句就表示将.cpp编译成对应的.o文件。实际上这个规则由两行组成,第二行指定了编译命令。解释一下这个命令,$(CXX)是使用最开始指定的编译器,-c,是将.cpp编译成.o时所必需的编译选项。$<表示自动推导文件依赖关系,-o,指定输出目标。$@表示目标,也就是.cpp的值。实际上.cpp会对应到多个.cpp文件,所以这条语句是对每个.cpp做这样的操作。后面的CXXFLAGS和INCPATH则是附加的编译选项。有人会把-c也放在CXXFLAGS里面,但我一般不这样做。我觉得g++ -c xx.cpp -o xx.o,这个顺序是比较清晰。
最后一部分则是编译过程的控制,由 目标:依赖文件 语句来控制。最开始是all,当在命令行终端下敲入make的时候,默认就是编译所有的,也就是由all指定的内容。我们所要生成的就是TARGET,所以把TARGET放在all的后面,不需要指定怎样由TARGET得到all 。接下来指定TARGET生成所依赖的文件, TARGET由OBJECTS生成,所以需要在下面一行,指定链接命令。注意,这一行有一个Tab空格。链接命令,由CXX进行链接,输入是$^,表示TARGET后面所指定的依赖文件,-o,指定输出,后面跟上链接选项和额外的库。这里把库放在LFALGS后面,并且LFLAGS里面只是链接选项,没有链接库。因为把所有的库都放在LIBPATH里面,指定库的路径,要链接的库等。不要把库的路径放在LIBPATH里面,然后把库的链接放在LFLAGS里面,最后按照LFLAGS,LIBPATH的顺序,就会提示找不到所要链接的库。
可以看到,这里的编译控制过程根据依赖关系,从最顶层,逐渐到最低层,查找到最底层的时候开始编译。当比较顶层的文件被修改时,底层的部分与它没有依赖关系,不需要从最底层开始编译。而最底层的被修改时,就需要从最底层开始,上面与其相关的都需要重新编译。
这只是一个比较简单的总结,Makefile还有很多东西,以后用熟悉了在继续总结。
学习Makefile比较好的参考:
[1]http://mrbook.org/tutorials/make/
[2]http://wiki.ubuntu.org.cn/%E8%B7%9F%E6%88%91%E4%B8%80%E8%B5%B7%E5%86%99Makefile
[3]http://os.51cto.com/art/200806/75991.htm
欢迎大家拍砖指导.
之前学习Makefile,一直忽略了多个Makefile的使用,就是在一个比较大的项目里,会有一个主Makefile来调用其它Makefile。现在有一份很好的Makefile的教程,可以参考http://www.codeproject.com/Articles/31488/Makefiles-in-Linux-An-Overview。
多个Makefile的时候,主要有两个问题,一个是Makefile被调用时shell所在的路径,另一个是其他Makefile里所定义的变量。
先看第一个问题,如果主Makefile中有下面的语句:
test:xxx xxx @pwd cd ./sub_dir @pwd
如果祝Makefile所在位置为/home/test,那么打印结果就是两行一样的,/home/test/,虽然执行了cd ./sub_dir。
这是因为
You should know that most shell commands like (cp, mv and so on) force make command:
- open a new instance of the shell;
- execute the command;
- close instance of the shell;
In fact, make creates three different instances of shell to process each of those commands.
那么,正确的做法是多条语句写在一行,用分号隔开,并用括号确保所有语句在一个shell里执行。如下,
test:xxx xxx (pwd;cd sub_dir;pwd)
就会得到所期望的结果,
/home/test/ /home/test/sub_dir/
下面看第二个问题,有了上面的解释,第二个问题应该也能理解了。如果变量是在主Makefile里面定义的,而被调用的Makefile是在新的shell里面执行,那么就无法访问那些定义的变量了(还需要验证).所以一般把共同的变量定义在一个make.inc里面,然后所有的Makefile里面可以包含这个文件,包含的路径需要注意。
暂时补充这些,等一些问题清楚了再进行完善。