1.2 程序编译工具:make
1.2.1 使用IDE编译C程序
在Windows下开发程序,我们一般会使用IDE(Integrated Development Environment,集成开发环境)。IDE提供了程序的编辑、编译、链接、运行、调试、项目管理一条龙服务。IDE将程序的编译、链接、运行等底层过程进行封装,留给用户的只有一个用户交互按钮:Run。用户甚至都不用关心程序到底是如何编译、链接和运行的,程序写好后,直接点击Run按钮,IDE会自动调用相关的预处理器、编译器、汇编器、链接器等工具生成可执行文件,并将可执行文件加载到内存运行,通过打印窗口,用户可以很直观地看到程序的运行结果。以C-Free 5.0集成开发环境为例,程序的编辑、编译、项目管理窗口的布局如图1-1所示。
笔者的C-Free 5.0安装在C:\Program Files (x86)\C-Free 5路径下,在安装目录下的C:\Program Files (x86)\C-Free 5\mingw\bin下面,你会看到很多二进制工具:as(汇编器)、cpp(预处理器)、ld(链接器)、gcc(GNU C编译器)、g++(GNU C++编译器)、gdb(调试器)、ar(归档工具,用来制作库)。当我们写好程序,点击窗口界面上的Run按钮时,C-Free 5.0在后台就会自动调用这些工具,生成可执行文件,并将其加载到内存运行。
这里需要注意的是:gcc并不是真正的C编译器,它是GNU C编译器工具集中的一个二进制工具。在编译程序时,gcc会首先运行,然后由gcc分别调用预处理器、编译器、汇编器、链接器等工具来完成整个编译过程。C-Free 5.0集成开发环境支持多个编译器配置,用户可以添加自己的编译器,然后通过gcc工具分别去调用它们。如C-Free 5.0默认安装的mingw32 C编译器cc1,安装在C:\Program Files (x86)\C-Free 5\mingw\libexec\gcc\mingw32\3.4.5目录下,当程序编译时,gcc就会根据用户指定的配置调用它,完成程序的编译过程。
图1-1 C-Free 5.0集成开发环境窗口布局说明
1.2.2 使用gcc编译C源程序
在Linux环境下编译程序和在Windows下不太一样,一般在命令行下编译代码。在Linux下,我们一般使用gcc或arm-linux-gcc交叉编译器来编译程序。在使用这些编译器之前,首先需要安装它们,在Ubuntu环境下,我们可以使用apt-get命令来安装这些编译工具。
安装完毕后,使用下面的命令可以查看编译器的版本。如果安装成功,则会有下面的显示信息。
工具安装成功后,我们就可以使用gcc或arm-linux-gnueabi-gcc命令来编译程序了。gcc是GCC编译器工具集中的一个应用程序,用来编译我们的C程序。如我们编写一个简单的C程序:
然后就可以使用gcc命令来编译main.c源程序文件了。
gcc在编译main.c源文件时,会依次调用预处理器、编译器、汇编器、链接器,最后生成可执行的二进制文件hello。根据需要,我们也可以通过gcc的编译参数来控制程序的编译过程。
● -E:只对C源程序进行预处理,不编译。
● -S:只编译到汇编文件,不再汇编。
● -c:只编译生成目标文件,不进行链接。
● -o:指定输出的可执行文件名。
● -g:生成带有调试信息的debug文件。
● -O2:代码编译优化等级,一般选择2。
● -W:在编译中开启警告(warning)信息。
● -I:大写的I,在编译时指定头文件的路径。
● -l:小写的l(like首字母),指定程序使用的函数库。
● -L:大写的L(like首字母),指定函数库的路径。
通过上面的这些参数,我们就可以根据实际需要来控制程序的编译过程。如上面的main.c源文件,如果我们只对其做编译操作,不链接,就可以使用下面的命令。
通过下面的命令,我们可以对C源文件main.c只做预处理操作,不再编译,并将预处理的结果重定向到main.i文件中。
打开main.i文件,你就可以看到一个原汁原味的纯C程序文件,在这个文件中,你可以看到一段C程序经过预处理后到底发生了什么变化。
1.2.3 使用make编译程序
我们可以在Shell环境下敲击gcc命令来编译C程序,也可以通过各种参数来控制编译流程。使用gcc编译程序非常方便,但是也有弊端:在一个多文件的项目中,如果C源文件过多,如编译Linux内核源码,大概有30 000多个C源文件,如果再使用gcc命令,恐怕就会变成下面这个样子了。
30 000多个文件,如果老板不拦着你,估计你能敲一天。如果每秒钟敲击一个文件名,30 000多个文件,就需要30 000多秒,光敲这个编译命令就得敲8个多小时。你从早上上班开始敲,一直敲到晚上下班,然后拍拍屁股走人。老板看了你的工作日志,估计肺都要气炸了,正拿着厨具飞奔在路上,还有5秒钟到达战场,非炒了你的鱿鱼不可!
针对多文件的编译难题,有没有更好的解决方法呢?有,使用make命令可以帮助我们提高程序的编译效率。
简单点理解,make其实也是一个编译工具,只不过它在编译程序时,要依赖一个叫作Makefile的文件:生成一个可执行文件所依赖的所有C源文件都在这个Makefile文件中指定。在Makefile中,通过定义一个个规则,来描述各个要生成的目标文件所依赖的源文件及编译命令,最后链接器将这些目标文件组装在一起,生成可执行文件。
当我们使用make编译一个工程时,make首先会解析Makefile,根据Makefile文件中定义的规则和依赖关系,分析出生成可执行文件和各个目标文件所依赖的源文件,并根据这些依赖关系构建出一个依赖关系树。然后根据这个依赖关系和Makefile定义的规则一步一步依次生成这些目标文件,最后将这些目标文件链接在一起,生成最终的可执行文件。
为了更好地理解make和Makefile,接下来我们举一个例子。在一个项目中,有main.c和sum.c两个C源文件。
如果我们使用gcc来编译程序,可以使用下面的命令。
如果我们想使用make工具来编译,首先要写一个Makefile。
一个Makefile通常是由一个个规则构成的,规则是构成Makefile的基本单元。一个规则通常由目标、目标依赖和命令3部分构成。
目标一般指我们要生成的可执行文件或各个源文件对应的目标文件。一个目标后面一般要紧跟生成这个目标所依赖的源文件,以及生成这个目标的命令。命令可以是编译命令,可以是链接命令,也可以是一个Shell命令,命令必须以Tab键开头。一个Makefile里可以有多个规则、多个目标,一般会选择第一个目标作为默认目标。
Makefile文件中使用.PHONY声明的目标是一个伪目标,伪目标并不是一个真正的文件名,可以看作一个标签。伪目标比较特殊,一般无依赖,主要用来无条件执行一些命令,如清理编译结果和中间临时文件。一个规则可以像伪目标那样无目标依赖,无条件地执行一些命令操作,也可以没有命令,只表示单纯的依赖关系,如上面Makefile文件中的all伪目标。
将Makefile文件放置在main.c和sum.c的同一目录下,然后进入该目录,在命令行环境下输入make命令就可以直接编译项目了,当然也可以使用make clean命令清除程序的编译结果和生成的临时中间文件。
这里有个细节需要注意:Makefile文件名的第一个字母一般要大写,当然使用小写也不会错,因为make在编译程序时会首先到当前目录下查找Makefile文件,找不到时再去找makefile或GNUmakefile文件,当这3个文件都找不到时,make就会报错。