利用makefile编译简单的C++工程

写在前面

       一部分windows程序员可能对makefie这个词很陌生,简单来说makefile是Linux/Unix环境下用于描述整个工程编译和连接规则的文件。在工程目录下你住需要输入make命令,编译器就会按照makefile中设计的规则进行编译最终生成一个可执行文件(如win下的.exe文件)。当然,如果你有一个足够强大的IDE如Visual Studio,每次按下编译执行快捷键的时候它已经做了这部分工作。
       最近因为一些需求,需要学习用makefile进行编译工作,所以这里写了一个demo来演示如何利用makefile编译一个简单的C++工程,闲言少叙。

正文

       首先我们先建立一个文件夹sample,用来存放工程文件。在其中建立一个main.cpp 作为工程的主程序,建立一个heads.h在其中声明一个函数,以及一个heads.cpp实现该函数,最后在main.cpp中调用这个函数。整个工程目录如图所示。
利用makefile编译简单的C++工程_第1张图片
代码比较少,这里就直接贴上来了。首先是main.cpp的内容:

/**
 * Makefile demo main.cpp
 * Created on 2019.2.14
 * */

#include 
#include "heads.h"

using namespace std;

int main()
{
    cout << "sum = " << add(36, 24) << endl;
    return 0;
}

然后是heads.h的内容:

#ifndef __HEADS_H_
#define __HEADS_H_

int add(int a, int b);

#endif

最后是heads.cpp的内容:

#include "heads.h"

int add(int a, int b)
{
    return a + b;
}

       根据程序,期望的输出应该是sum = 60,这里我们先不要急着建立makefile,先考虑一下如果通过命令行应该如何编译这些文件。本例子准本选用clang作为编译器,那么在终端输入clang++ -std=c++11 heads.cpp main.cpp -o hello。接着输入ls命令查看我们的工程目录,会发现多了一个名为hello的可执行文件,运行hello,发现程序的输出是。
运行结果
这说明我们的程序被成功的编译并运行了。
       接下来在目录中新建一个名为“Makefile”的文件(注意大小写,建议如此命名,没有后缀)。把上文的编译命令copy进去,得到的Makefile内容如下:

all:
	clang++ -std=c++11 heads.cpp main.cpp -o hell
prog2:

进入终端,cd到sample目录下,删除之前的hello文件,输入make,终端会输出:
clang++ -std=c++11 heads.cpp main.cpp -o hello
然后会发现工程目录下生成了一个新的hello文件,运行该文件会得到和上文一样的结果。接下来我们试着将makefile中的这行命令剪切到prog2下,然后在终端中输入make prog2,会发现

all:
prog2:
	clang++ -std=c++11 heads.cpp main.cpp -o hell

得到的结果也是一样的。
       到这里我们可以明白了,makefile是一个脚本,它能够帮助你执行各种编译命令,虽然这些都是你自己写上去的,但是考虑到如果一个工程中有几十上百个文件,只要受一次累之后都可以轻松编译的话还是很值的。那么这个脚本中冒号前面的就是目标(target),下面的一行是命令。在终端输入make时,makefile程序自动寻找到第一个目标(all)并执行该目标下的命令,在输入make prog2时,由于我们特定了目标,所以makefile程序会定位到prog2的位置开始执行命令。所以整个Makefile的结构就可以抽象成如下的形式:

target... : prerequisites...
[TAB]commands
[TAB]...

其中target就是上文提到的目标,commands就是clang++…等的命令,注意命令前面要用一个[TAB]键来空格
       那么这个prerequisites又是什么呢?回看sample的工程目录,我们可以知道目标文件hello是由main.cpp和heads.cpp组成的,而main.cpp和heads.cpp又都依赖于heads.h头文件,理清了这个关系之后可以发现这个prerequisites就是生成该目标所需要的目标文件、源文件、头文件等。根据这一点Makefile就可以改写成如下的形式:

hello : main.o heads.o
	clang++ -std=c++11 heads.o main.o -o hello
main.o : main.cpp heads.h
	clang++ -std=c++11 -c main.cpp
heads.o : heads.cpp heads.h
	clang++ -std=c++11 -c heads.cpp

这个脚本的逻辑用自然语言来描述就是,我需要将main.o和heads.o链接生成可执行文件hello,而main.o和heads.o是根据它们的源代码文件(.cpp)和heads.h通过命令clang++ -std=c++11 -c生成得来的。此时在终端输入make命令,终端会输出:

clang++ -std=c++11 -c main.cpp
clang++ -std=c++11 -c heads.cpp
clang++ -std=c++11 heads.o main.o -o hello

命令执行的顺序和上文描述的逻辑是一样的,此时会发现目录下会多出两个文件分别叫main.o和heads.o,这两个目标文件就是前两条命令生成的了,至于什么是目标文件(.obj/.o)这里就不展开说了,请自行百度。
       到目前为止利用makefile编译简单工程的基本逻辑已经通顺了,接下来需要做的就是利用makefile的各种便利功能来简化脚本,削减工作量。首先用到的就是变量,makefile中的变量可以理解为一种等价代换,将经常用到的命令、文件、目标等等归拢到一个变量中,方便复用也方便修改。那么这个工程的Makefile可以改写为如下的形式:

objects = main.o heads.o
CPP = clang++
CPPFLAGS = -std=c++11

hello : $(objects)
	$(CPP) $(CPPFLAGS) $(objects) -o hello
main.o : main.cpp heads.h
	$(CPP) $(CPPFLAGS) -c main.cpp
heads.o : heads.cpp heads.h
	$(CPP) $(CPPFLAGS) -c heads.cpp

这里把目标文件、编译器、编译器选项都分别赋值给了一个变量在下面调用。接下来利用到的就是makefile强大的自动推导功能了,举例来说,想要生成main.o肯定要利用到main.cpp,脚本中main.cpp就是多余的,而且生成.o文件的也只有这一条命令,所以这条命令也是多余的,不用你强调,makefile自己也知道该如何做。那么我们就可以把该省略的都省略,最后添加一个清除不要的.o文件的命令,整个Makefile就算大功告成了。关于clean的部分这里就不做细讲了,需要清理的时候只要在终端输入make clean即可,希望深入了解的同学请移步参考资料[1]自行查阅。

objects = main.o heads.o
CPP = clang++
CPPFLAGS = -std=c++11

hello : $(objects)
	$(CPP) $(CPPFLAGS) $(objects) -o hello
main.o : heads.h
heads.o : heads.h

#清除.o文件
.PHONY : clean
clean :
	-rm $(objects)

总结

       以上便是如何利用makefile编译简单C++工程的全部内容,记录在此作为一个总结,也希望能给不知如何使用makefile编译的人一些帮助。其实上文的Makefile仍然又精简的余地,但是对于这个工程来说个人认为已经足够了,依赖明确,逻辑关系也清楚。makefile是一个非常强大的工具,文中介绍的内容也只是基础中的基础,学无止境。

参考资料

[1]http://wiki.ubuntu.org.cn/跟我一起写Makefile:MakeFile介绍#makefile.E7.9A.84.E8.A7.84.E5.88.99
[2]https://www.youtube.com/watch?v=i3tYp88YHbI&t=317s
(油管视屏,要)

你可能感兴趣的:(利用makefile编译简单的C++工程)