首页 百科知识 如何写一个可编译的文件

如何写一个可编译的文件

时间:2022-10-09 百科知识 版权反馈
【摘要】:大部分Unix和Linux通常利用make工具来自动完成编译工作。如果不特别指定,make命令在执行时将按顺序查找默认的makefile文件。多数Linux程序员使用第三种文件名Makefile。目标,即make最终需要创建的文件,如可执行文件和目标文件;目标也可以是要执行的动作,如“clean”。当make执行完所有这些嵌套的规则后,make将处理最顶层的test规则。在上面Makefile的例子中,还定义了一个目标clean,它是Makefile中常用的一种专用目标,即删除所有的目标模块。

14.4 Makefile的创建方法

在大型的开发项目中,通常有几十到上百个的源文件,如果每次均手工输入gcc命令进行编译的话,则会非常不方便。大部分Unix和Linux通常利用make工具来自动完成编译工作。make工具会读取一个包含指令的文件(这个文件的名字通常都是makefile或Makefile),并执行各种操作来编译程序

make工具有多种,如GNU make、System V make和Berkeley make。make是用来组织应用程序编译过程的基本工具,但是每个make工具之间又有所不同。在此,我们介绍GNU make。

要使用make,必须编写一个叫做Makefile的文件,这个文件描述了软件包中文件之间的关系,提供更新每个文件的命令。make实际上是根据Makefile来进行编译的。一般在一个软件包里,通常是可执行文件靠目标文件来更新,目标文件靠编译源文件来更新。我们可以按一定的规则将编译命令写到Makefile文件中,每次需要重新编译时只需执行make命令,就会自动搜索Makefile文件,读取其中的内容,从而完成编译任务。

Makefile写好之后,每次改变某些源文件,只要执行make命令:

# make

所有必要的重新编译将执行。make程序利用makefile中的数据和每个文件的最后修改时间来确定哪个文件需要更新,对于需要更新的文件,make程序执行makefile数据中定义的命令来更新。

makefile的默认文件名为GNUmakefile、makefile或Makefile,当然也可以在make的命令行中指定别的文件名。如果不特别指定,make命令在执行时将按顺序查找默认的makefile文件。多数Linux程序员使用第三种文件名Makefile。因为第一个字母是大写,通常被列在一个目录文件列表的最前面。

Makefile是一个文本形式的数据库文件,其中包含一些规则来告诉make处理哪些文件以及如何处理这些文件。这些规则主要是描述哪些文件(称为target目标文件,不要和编译时产生的目标文件相混淆)是从哪些别的文件(称为dependency依赖文件)中产生的,以及用什么命令(command)来执行这个过程。

make对磁盘上的文件进行检查,如果目标文件的生成或被改动时的时间(称为该文件时间戳)至少比它的一个依赖文件还旧的话,make就执行相应的命令,以更新目标文件。目标文件不一定是最后的可执行文件,可以是任何一个中间文件并可以作为其他目标文件的依赖文件。

一个Makefile文件主要含有一系列的规则,每条规则包含以下内容:

(1)目标(target),即make最终需要创建的文件,如可执行文件和目标文件;目标也可以是要执行的动作,如“clean”。

(2)一个或多个依赖文件(dependency)列表,通常是编译目标文件所需要的其他文件。

(3)一系列命今(command),是make执行的动作,通常是把指定的相关文件编译成目标文件的编译命令,每个命令占一行,且每个命令行的起始字符必须为TAB字符。

除非特别指定,否则make的工作目录就是当前目录。target是需要创建的二进制文件或目标文件,dependency是在创建target时需要用到的一个或多个文件的列表,命令序列是创建target文件所需要执行的步骤,比如编译命令。

Makefile规则的一般形式如下:

target:dependency dependency

(tab)〈command〉

下面是一个简单的Makefile的例子:

# 以#开头的为注释行

test:prog.o code.o

  gcc –o test prog.o code.o

prog.o:prog.c prog.h code.h

  gcc –c prog.c –o prog.o

code.o:code.c code.h

  gcc –c code.c –o code.o

clean:

  rm –f *.o

Makefile文件中共定义了四个目标:test、prog.o、code.o和clean。目标从每行的最左边开始写,后面跟一个冒号(:),如果有与这个目标有依赖性的其他目标或文件,把它们列在冒号后面,并以空格隔开。然后另起一行开始写实现这个目标的一组命令。在Makefile中,可使用续行号(\)将一个单独的命令行延续成几行。但要注意在续行号(\)后面不能跟任何字符(包括空格键)。

一般情况下,调用make命令可输入:

# make target

target是Makefile文件中定义的目标之一,如果省略target,make就将生成Makefile文件中定义的第一个目标。对于上面Makefile的例子,单独的一个“make”命令等价于:

# make test

因为test是Makefile文件中定义的第一个目标,make首先将其读入,然后从第一行开始执行,把第一个目标test作为它的最终目标,所有后面的目标的更新都会影响到test的更新。第一条规则说明只要文件test的时间戳比文件prog.o或code.o中的任何一个旧,下一行的编译命令将会被执行。但是,在检查文件prog.o和code.o的时间戳之前,make会在下面的行中寻找以prog.o和code.o为目标的规则,在第三行中找到了关于prog.o的规则,该文件的依赖文件是prog.c、prog.h和code.h。同样,make会在后面的规则行中继续查找这些依赖文件的规则,如果找不到,则开始检查这些依赖文件的时间戳,如果这些文件中任何一个的时间戳比prog.o的新,make将执行“gcc –c prog.c –o prog.o”命令,更新prog.o文件。

以同样的方法,接下来对文件code.o做类似的检查,依赖文件是code.c和code.h。当make执行完所有这些嵌套的规则后,make将处理最顶层的test规则。如果关于prog.o和code.o的两个规则中的任何一个被执行,至少其中一个.o目标文件就会比test新,那么就要执行test规则中的命令,因此make去执行gcc命令将prog.o和code.o连接成目标文件test。

在上面Makefile的例子中,还定义了一个目标clean,它是Makefile中常用的一种专用目标,即删除所有的目标模块。

make做的工作:首先make按顺序读取makefile中的规则,然后检查该规则中的依赖文件与目标文件的时间戳哪个更新,如果目标文件的时问戳比依赖文件还早,就按规则中定义的命令更新目标文件。如果该规则中的依赖文件又是其他规则中的目标文件,那么依照规则链不断执行这个过程,直到Makefile文件的结束,至少可以找到一个不是规则生成的最终依赖文件,获得此文件的时间戳,然后从下到上依照规则链执行目标文件的时间戳比此文件时间戳旧的规则,直到最顶层的规则。

通过以上的分析过程,可以看到make的优点,因为.o目标文件依赖.c源文件,源码文件里一个简单改变都会造成那个文件被重新编译,并根据规则链依次由下到上执行编译过程,直到最终的可执行文件被重新链接。例如,当改变一个头文件的时候,由于所有的依赖关系都在Makefile里,因此不再需要记住依赖此头文件的所有源码文件,make可以自动地重新编译所有那些因依赖这个头文件而改变了的源码文件,如果需要,再进行重新链接。

Makefile里的变量就像一个环境变量。事实上,环境变量在make中也被解释成make的变量。这些变量对大小写敏感,一般使用大写宇母。几乎可以从任何地方引用定义的变量,变量的主要作用如下:

保存文件名列表。在前面的例子里,作为依赖文件的一些目标文件名出现在可执行文件的规则中,而在这个规则的命令行里同样包含这些文件并传递给gcc作为命令参数。如果使用一个变量来保存所有的目标文件名,则可以方便地加入新的目标文件而且不易出错。

保存可执行命令名,如编译器。在不同的Linux系统中存在着很多相似的编译器系统,这些系统在某些地方会有细微的差别,如果项目被用在一个非gcc的系统里,则必须将所有出现编译器名的地方改成用新的编译器名。但是如果使用一个变量来代替编译器名,那么只需要改变该变量的值,其他所有地方的命令名就都改变了。

保存编译器的参数。在很多源代码编译时,gcc需要很长的参数选项,在很多情况下,所有的编译命令使用一组相同的选项,如果把这组选项使用一个变量代表,那么可以把这个变量放在所有引用编译器的地方。当要改变选项的时候,只需改变一次这个变量的内容即可。

Makefile中的变量是用一个文本串在Makefile中定义的,这个文本串就是变量的值。只要在一行的开始写下这个变量的名字,后面跟一个“=”号,以及要设定这个变量的值即可定义变量,下面是定义变量的语法:

VARNAME=string

使用时,把变量用括号括起来,并在前面加上$符号,就可以引用变量的值:

${VARNAME}

make解释规则时,VARNAME在等式右端展开为定义它的字符串。变量一般都在Makefile的头部定义。按照惯例,所有的Makefile变量都应该是大写。如果变量的值发生变化,就只需要在一个地方修改,从而简化了Makefile的维护。

现在利用变量把前面的Makefile重写一遍:

OBJS=prog.o code.o

CC=gcc

test:${ OBJS }

  ${ CC } –o test ${ OBJS }

prog.o:prog.c prog.h code.h

  ${ CC } –c prog.c –o prog.o

code.o:code.c code.h

  ${ CC } –c code.c –o code.o

clean:

  rm –f *.o

除用户自定义的变量外,make还允许使用环境变量、自动变量和预定义变量。使用环境变量的方法很简单,在make启动时,make读取系统当前已定义的环境变量,并且创建与之同名同值的变量,因此用户可以像在shell中一样在Makefile中方便地引用环境变量。需要注意的是,如果用户在Makefile中定义了同名的变量,用户自定义变量将覆盖同名的环境变量。此外,Makefile中还有一些预定义变量和自动变量,但是看起来并不像自定义变量那样直观。

在上面的例子中,几个产生目标文件的命令都是从“.c”的C语言源文件和相关文件通过编译产生“.o”目标文件,这也是一般的步骤。实际上,make可以使工作更加自动化,也就是说,make知道一些默认的动作,它有一些称作隐含规则的内置的规则,这些规则告诉make当用户没有完整地给出某些命令的时候,应该怎样执行。

例如,把生成prog.o和code.o的命令从规则中删除,make将会查找隐含规则,然后会找到并执行一个适当的命令。由于这些命令会使用一些变量,因此可以通过改变这些变量来定制make。像在前面例子中所定义的那样,make使用变量CC来定义编译器,并且传递变量CFLAGS(编译器参数)、CPPFLAGS(C语言预处理器参数)、TARGET_ARCH(目标机器的结构定义)给编译器,然后加上参数-c,后面跟变量〈(第一个依赖文件名),然后是参数-o加变量$@(目标文件名)。综上所述,一个C语言编译的具体命令将会是:

$ {CC} $ {CFLAGS} $ {CPPFLAGS} $ {TARGET_ARCH} –c 〈 -o $@

在上面的例子中,利用隐含规则,可以简化为:

OBJS=prog.o code.o

CC=gcc

test:${ OBJS }

  ${ CC } –o $@ $^

prog.o:prog.c prog.h code.h

code.o:code.c code.h

clean:

  rm –f *.o

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈