GNU Make¶
约 1950 个字 13 行代码 预计阅读时间 7 分钟
Abstract
GNU Make是一个用于自动化编译的工具,它可以根据文件的依赖关系自动执行编译任务.本文将介绍GNU Make的基本使用方法.
为啥要学Make?
属于是为了这盘醋包了顿饺子,由于数据结构基础这门课的大作业或多或少需要用到多文件编译,我干脆就希望使用Make进行自动化编译,而且在系统课上接触了Makefile的编写,所以就有了这篇文章.
Part 1 别急,先看看 gcc 编译!¶
gcc的编译过程可以简要分为四个阶段:预处理、编译、汇编、链接.
gcc编译工具链是以gcc编译器为核心的一整套工具,主要包含以下三部分内容:
- gcc-core:亦即gcc编译器,用于完成预处理过程和编译过程,将C代码转换成汇编代码;
- Binutils:包含了除了gcc编译器以外的一系列小工具,比如汇编器as、连接器ld、目标文件格式查看器readelf等;
- glibc:GNU C Library,是GNU组织为了GNU系统以及Linux系统编写的C语言标准库.
1.1 预处理/Pre-Processing¶
预处理阶段的主要任务是处理源文件以#
开头的预处理指令,比如#include
、#define
等.这里主要是将#include
的一些头文件与宏定义进行展开,生成一个.i
文件.
预处理过程输入的是C的源文件,输出的是一个中间/预加载文件,这个文件还是C代码.此阶段使用gcc参数-E
,同时参数-o
指定了最后输出文件的名字,下面的例子就将main.c
文件经过预处理生成main.i
文件:
1.2 编译/Compiling¶
编译过程使用gcc编译器将预处理后的.i
文件通过编译转换为汇编语言,生成一个.s
文件.这是gcc编译器完成的工作,在这部分过程之中,gcc编译器会检查各个源文件的语法,即使我们调用了一个没有定义的函数,也不会报错,
编译过程输入的是一个中间/预加载文件,输出的是一个汇编文件,当然,直接以C文件作为输入进行编译也是可以的.此阶段使用gcc参数-S
,具体例子如下:
1.3 汇编/Assembling¶
汇编阶段的主要任务是将汇编语言文件经过汇编,生成目标文件.o
文件,每一个源文件都对应一个目标文件.即把汇编语言的代码转换成机器码,这是as汇编器完成的工作.
汇编过程输入的是汇编文件,输出.o
后缀的目标文件,gcc的参数-c
表示只编译源文件但不链接,当然,我们也可以直接输入C源文件,就直接包含了前面两个过程.
Linux下生成的.o
目标文件、.so
动态库文件以及下一小节链接阶段生成最终的可执行文件都是elf格式的, 可以使用 readelf 工具来查看它们的内容.
从 readelf 的工具输出的信息,可以了解到目标文件包含ELF头、程序头、节等内容,对于.o
目标文件或.so
库文件,编译器在链接阶段利用这些信息把多个文件组织起来,对于可执行文件,系统在运行时根据这些信息加载程序运行.
1.4 链接/Linking¶
最后将每个源文件对应的.o
文件链接起来,就生成了一个可执行程序文件,这是这是链接xx器ld完成的工作.
例如一个工程里包含了A和B两个代码文件,在链接阶段,链接过程需要把A和B之间的函数调用关系理顺,也就是说要告诉A在哪里能够调用到fun
函数,建立映射关系,所以称之为链接.若链接过程中找不到fun
函数的具体定义,则会链接报错.
链接分为两种:
- 动态链接:gcc编译时的默认选项.动态是指在应用程序运行时才去加载外部的代码库,不同的程序可以共用代码库.所以动态链接生成的程序比较小,占用较少的内存.
- 静态链接:链接时使用选项
--static
,它在编译阶段就会把所有用到的库打包到自己的可执行程序中.所以静态链接的优点是具有较好的兼容性,不依赖外部环境,但是生成的程序比较大.
Part 2 Makefile¶
2.1 书写规则¶
Makefile的规则包括两个部分:一个是依赖关系/prerequisites,另一个是生成目标的方法/command.在Makefile中,规则的顺序是很重要的,Makeflie中有且仅有一个最终目标,其他目标都是这个目标连带出来的,一般来说,定义在第一条规则的第一个目标就是最终目标,make完成的就是这个目标.
但是我们经常会遇见make a.o
这样的命令,make后可以跟着一个或多个target,a.o
作为make的参数,指定了执行的内容,优先级比Makefile里边的定义要高.换句话说,倘若make后边有目标,这个目标就是最终目标,make后边没目标,默认执行Makefile的第一个目标.
规则的语法是这样的:
或者这样的:
targets是文件名,可以使用通配符,基本来说,我们的目标基本上是一个文件,可以是一个目标文件,可以是一个可执行文件,还可以是一个标签,是多个文件也是有可能的.
prerequisites是生成该target所依赖的文件或者target.
recipe是命令行,可以是任意的shell命令,如果其不与target:prerequisites
在一行,那么,必须以 Tab
键开头,如果和prerequisites
在一行,那么可以用分号做为分隔.如果命令太长,我们可以使用反斜杠\
来作为换行符.
规则告诉make两件事:一个是文件的依赖关系,target依赖于prerequisites中的文件;另一个时就会如何生成目标文件,生成规则定义在recipe中,makefile最核心的内容是:
prerequisites中如果有一个以上的文件比target文件要新的话,recipe所定义的命令就会被执行.
2.2 使用变量¶
Makefile中的变量就像是C语言的宏一样,代表着一个文本字符串,在执行的时候会自动展开在所使用的地方,不过,我们可以在Makefile中改变其值.变量可以使用在目标,规则,依赖目标或者其他部分之中.
在声明变量的时候,我们需要给予其初值,使用的时候需要在变量名前面加上$
符号,最好还用小括号括起来()
,变量的名字可以包含字符,数字,下划线,甚至还可以数字开头,但是不能含有:
,#
,=
或者空字符.
我们可以使用其他变量来构造变量的值,比如foo = $(bar)
,这里的bar
不一定非要是已经定义好的值,我们可以使用后面定义的值,这就很好了嘛,我们可以把变量的真实值退到后面去定义,但是无法避免递归定义,虽然make有能力检测这样的定义。
为了避免这个问题,我们使用:=
操作符,对于VAR := value
,右边的value
会在定义的时候就被展开.