Linux下C编程基础之:make工程管理器
扫描二维码
随时随地手机看文章
到此为止,读者已经了解了如何在Linux下使用编辑器编写代码,如何使用gcc把代码编译成可执行文件,还学习了如何使用gdb来调试程序,那么,所有的工作看似已经完成了,为什么还需要make这个工程管理器呢?
所谓工程管理器,顾名思义,是用于管理较多的文件。读者可以试想一下,由成百上千个文件构成的项目,如果其中只有一个或少数几个文件进行了修改,按照之前所学的gcc编译工具,就不得不把这所有的文件重新编译一遍,因为编译器并不知道哪些文件是最近更新的,而只知道需要包含这些文件才能把源代码编译成可执行文件,于是,程序员就不得不重新输入数目如此庞大的文件名以完成最后的编译工作。
编译过程分为编译、汇编、链接阶段,其中编译阶段仅检查语法错误以及函数与变量是否被正确地声明了,在链接阶段则主要完成函数链接和全局变量的链接。因此,那些没有改动的源代码根本不需要重新编译,而只要把它们重新链接进去就可以了。所以,人们就希望有一个工程管理器能够自动识别更新了的文件代码,而不需要重复输入冗长的命令行,这样,make工程管理器就应运而生了。
实际上,make工程管理器也就是个“自动编译管理器”,这里的“自动”是指它能够根据文件时间戳自动发现更新过的文件而减少编译的工作量,同时,它通过读入makefile文件的内容来执行大量的编译工作。用户只需编写一次简单的编译语句就可以了。它大大提高了实际项目的工作效率,而且几乎所有Linux下的项目编程均会涉及它,希望读者能够认真学习本节内容。
3.5.1makefile基本结构makefile是make读入的惟一配置文件,因此本节的内容实际就是讲述makefile的编写规则。在一个makefile中通常包含如下内容:
n 需要由make工具创建的目标体(target),通常是目标文件或可执行文件;
n 要创建的目标体所依赖的文件(dependency_file);
n 创建每个目标体时需要运行的命令(command),这一行必须以制表符(tab键)开头。
它的格式为:
target:dependency_files
command/*该行必须以tab键开头*/
例如,有两个文件分别为hello.c和hello.h,创建的目标体为hello.o,执行的命令为gcc编译指令:gcc–chello.c,那么,对应的makefile就可以写为:
#Thesimplestexample
hello.o:hello.chello.h
gcc–chello.c–ohello.o
接着就可以使用make了。使用make的格式为:maketarget,这样make就会自动读入makefile(也可以是首字母大写的Makefile)并执行对应target的command语句,并会找到相应的依赖文件。如下所示:
[root@localhostmakefile]#makehello.o
gcc–chello.c–ohello.o
[root@localhostmakefile]#ls
hello.chello.hhello.omakefile
可以看到,makefile执行了“hello.o”对应的命令语句,并生成了“hello.o”目标体。
注意
在makefile中的每一个command前必须有“Tab”符,否则在运行make命令时会出错。
3.5.2makefile变量上面示例的makefile在实际中是几乎不存在的,因为它过于简单,仅包含两个文件和一个命令,在这种情况下完全不必要编写makefile而只需在shell中直接输入即可,在实际中使用的makefile往往是包含很多的文件和命令的,这也是makefile产生的原因。下面就可给出稍微复杂一些的makefile进行讲解。
david:kang.oyul.o
gcckang.obar.o-omyprog
kang.o:kang.ckang.hhead.h
gcc–Wall–O-g–ckang.c-okang.o
yul.o:bar.chead.h
gcc-Wall–O-g–cyul.c-oyul.o
在这个makefile中有3个目标体(target),分别为david、kang.o和yul.o,其中第一个目标体的依赖文件就是后两个目标体。如果用户使用命令“makedavid”,则make管理器就是找到david目标体开始执行。
这时,make会自动检查相关文件的时间戳。首先,在检查“kang.o”、“yul.o”和“david”3个文件的时间戳之前,它会向下查找那些把“kang.o”或“yul.o”作为目标文件的时间戳。比如,“kang.o”的依赖文件为“kang.c”、“kang.h”、“head.h”。如果这些文件中任何一个的时间戳比“kang.o”新,则命令“gcc–Wall–O-g–ckang.c-okang.o”将会执行,从而更新文件“kang.o”。在更新完“kang.o”或“yul.o”之后,make会检查最初的“kang.o”、“yul.o”和“david”3个文件,只要文件“kang.o”或“yul.o”中的至少有一个文件的时间戳比“david”新,则第二行命令就会被执行。这样,make就完成了自动检查时间戳的工作,开始执行编译工作。这也就是make工作的基本流程。
接下来,为了进一步简化编辑和维护makefile,make允许在makefile中创建和使用变量。变量是在makefile中定义的名字,用来代替一个文本字符串,该文本字符串称为该变量的值。在具体要求下,这些值可以代替目标体、依赖文件、命令以及makefile文件中其他部分。在makefile中的变量定义有两种方式:一种是递归展开方式,另一种是简单方式。
递归展开方式定义的变量是在引用该变量时进行替换的,即如果该变量包含了对其他变量的引用,则在引用该变量时一次性将内嵌的变量全部展开,虽然这种类型的变量能够很好地完成用户的指令,但是它也有严重的缺点,如不能在变量后追加内容(因为语句:CFLAGS=$(CFLAGS)-O在变量扩展过程中可能导致无穷循环)。
为了避免上述问题,简单扩展型变量的值在定义处展开,并且只展开一次,因此它不包含任何对其他变量的引用,从而消除变量的嵌套引用。
递归展开方式的定义格式为:VAR=var。
简单扩展方式的定义格式为:VAR:=var。
make中的变量使用均使用的格式为:$(VAR)。
注意
变量名是不包括“:”、“#”、“=”以及结尾空格的任何字符串。同时,变量名中包含字母、数字以及下划线以外的情况应尽量避免,因为它们可能在将来被赋予特别的含义。
变量名是大小写敏感的,例如变量名“foo”、“FOO”、和“Foo”代表不同的变量。
推荐在makefile内部使用小写字母作为变量名,预留大写字母作为控制隐含规则参数或用户重载命令选项参数的变量名。
下面给出了上例中用变量替换修改后的makefile,这里用OBJS代替kang.o和yul.o,用CC代替gcc,用CFLAGS代替“-Wall-O–g”。这样在以后修改时,就可以只修改变量定义,而不需要修改下面的定义实体,从而大大简化了makefile维护的工作量。
经变量替换后的makefile如下所示:
OBJS=kang.oyul.o
CC=gcc
CFLAGS=-Wall-O-g
david:$(OBJS)
$(CC)$(OBJS)-odavid
kang.o:kang.ckang.h
$(CC)$(CFLAGS)-ckang.c-okang.o
yul.o:yul.cyul.h
$(CC)$(CFLAGS)-cyul.c-oyul.o
可以看到,此处变量是以递归展开方式定义的。
makefile中的变量分为用户自定义变量、预定义变量、自动变量及环境变量。如上例中的OBJS就是用户自定义变量,自定义变量的值由用户自行设定,而预定义变量和自动变量为通常在makefile都会出现的变量,它们的一部分有默认值,也就是常见的设定值,当然用户可以对其进行修改。
预定义变量包含了常见编译器、汇编器的名称及其编译选项。表3.15列出了makefile中常见预定义变量及其部分默认值。
表3.15 makefile中常见的预定义变量
预定义变量
含义
AR
库文件维护程序的名称,默认值为ar
AS
汇编程序的名称,默认值为as
CC
C编译器的名称,默认值为cc
CPP
C预编译器的名称,默认值为$(CC)–E
CXX
C++编译器的名称,默认值为g++
FC
Fortran编译器的名称,默认值为f77
RM
文件删除程序的名称,默认值为rm–f
ARFLAGS
库文件维护程序的选项,无默认值
ASFLAGS
汇编程序的选项,无默认值
CFLAGS
C编译器的选项,无默认值
CPPFLAGS
C预编译的选项,无默认值
CXXFLAGS
C++编译器的选项,无默认值
FFLAGS
Fortran编译器的选项,无默认值
可以看出,上例中的CC和CFLAGS是预定义变量,其中由于CC没有采用默认值,因此,需要把“CC=gcc”明确列出来。
由于常见的gcc编译语句中通常包含了目标文件和依赖文件,而这些文件在makefile文件中目标体所在行已经有所体现,因此,为了进一步简化makefile的编写,就引入了自动变量。自动变量通常可以代表编译语句中出现目标文件和依赖文件等,并且具有本地含义(即下一语句中出现的相同变量代表的是下一语句的目标文件和依赖文件)。表3.16列出了makefile中常见的自动变量。
表3.16 makefile中常见的自动变量
自动变量
含义
$*
不包含扩展名的目标文件名称
$+
所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件
$<
第一个依赖文件的名称
$?
所有时间戳比目标文件晚的依赖文件,并以空格分开
$@
目标文件的完整名称
$^
所有不重复的依赖文件,以空格分开
$%
如果目标是归档成员,则该变量表示目标的归档成员名称
自动变量的书写比较难记,但是在熟练了之后使用会非常方便,请读者结合下例中的自动变量改写的makefile进行记忆。
OBJS=kang.oyul.o
CC=gcc
CFLAGS=-Wall-O-g
david:$(OBJS)
$(CC)$^-o$@
kang.o:kang.ckang.h
$(CC)$(CFLAGS)-c$<-o$@
yul.o:yul.cyul.h
$(CC)$(CFLAGS)-c$<-o$@
另外,在makefile中还可以使用环境变量。使用环境变量的方法相对比较简单,make在启动时会自动读取系统当前已经定义了的环境变量,并且会创建与之具有相同名称和数值的变量。但是,如果用户在makefile中定义了相同名称的变量,那么用户自定义变量将会覆盖同名的环境变量。
3.5.3makefile规则makefile的规则是make进行处理的依据,它包括了目标体、依赖文件及其之间的命令语句。在上面的例子中,都显式地指出了makefile中的规则关系,如“$(CC)$(CFLAGS)-c$<-o$@”,但为了简化makefile的编写,make还定义了隐式规则和模式规则,下面就分别对其进行讲解。
1.隐式规则隐含规则能够告诉make怎样使用传统的规则完成任务,这样,当用户使用它们时就不必详细指定编译的具体细节,而只需把目标文件列出即可。make会自动搜索隐式规则目录来确定如何生成目标文件。如上例就可以写成:
OBJS=kang.oyul.o
CC=gcc
CFLAGS=-Wall-O-g
david:$(OBJS)
$(CC)$^-o$@
为什么可以省略后两句呢?因为make的隐式规则指出:所有“.o”文件都可自动由“.c”文件使用命令“$(CC)$(CPPFLAGS)$(CFLAGS)-cfile.c–ofile.o”来生成。这样“kang.o”和“yul.o”就会分别通过调用“$(CC)$(CFLAGS)-ckang.c-okang.o”和“$(CC)$(CFLAGS)-cyul.c-oyul.o”来生成。
注意
在隐式规则只能查找到相同文件名的不同后缀名文件,如“kang.o”文件必须由“kang.c”文件生成。
表3.17给出了常见的隐式规则目录。
表3.17 makefile中常见隐式规则目录
对应语言后缀名
隐式规则
C编译:.c变为.o
$(CC)–c$(CPPFLAGS)$(CFLAGS)
C++编译:.cc或.C变为.o
$(CXX)-c$(CPPFLAGS)$(CXXFLAGS)
Pascal编译:.p变为.o
$(PC)-c$(PFLAGS)
Fortran编译:.r变为-o
$(FC)-c$(FFLAGS)
2.模式规则模式规则是用来定义相同处理规则的多个文件的。它不同于隐式规则,隐式规则仅仅能够用make默认的变量来进行操作,而模式规则还能引入用户自定义变量,为多个文件建立相同的规则,从而简化makefile的编写。
模式规则的格式类似于普通规则,这个规则中的相关文件前必须用“%”标明。使用模式规则修改后的makefile的编写如下:
OBJS=kang.oyul.o
CC=gcc
CFLAGS=-Wall-O-g
david:$(OBJS)
$(CC)$^-o$@
%.o:%.c
$(CC)$(CFLAGS)-c$<-o$@
3.5.4make管理器的使用使用make管理器非常简单,只需在make命令的后面键入目标名即可建立指定的目标,如果直接运行make,则建立makefile中的第一个目标。
此外make还有丰富的命令行选项,可以完成各种不同的功能。表3.18列出了常用的make命令行选项。
表3.18 make的命令行选项
命令格式
含义
-Cdir
读入指定目录下的makefile
-ffile
读入当前目录下的file文件作为makefile
-I
忽略所有的命令执行错误
-Idir
指定被包含的makefile所在目录
-n
只打印要执行的命令,但不执行这些命令
-p
显示make变量数据库和隐含规则
-s
在执行命令时不显示命令
-w
如果make在执行过程中改变目录,则打印当前目录名