写在最前面:所有的博文都是为了若干年月以后当我再次翻看可以快速回想起之前的零星知识。学海无涯,在看这篇文章的未来的你,加油吧!
目录
一、Makefile简介
二、Linux环境下的C语言编译过程
2.1 gcc编译流程
2.2 gcc常用参数解释
三、makefile基本语法
3.1 makefile基本语法
3.2 实战栗子
一、Makefile简介
Makefile是一种用于编译的脚本语言,它可以很便捷地管理项目的代码编译。如果项目中某些源文件被修改则只编译这几个被修改的源文件。又或者某个头文件被修改,则重新编译所有包含该头文件的源文件。文件夹内的makefile文件可以写做makefile或者Makefile。
二、Linux环境下的C语言编译过程
为了更好地理解makefile的编译工作流程,首先要搞明白一次完整的c语言的编译过程是什么样子的。gcc是我们常用的编译器,我们以它为例进行说明。
2.1 gcc编译流程
一个c源文件最终只有生成可执行文件才能被机器正确执行。使用gcc编译程序时,该过程分为以下四个阶段:预处理、编译、汇编、链接。假设我们有一个c源文件:啊对对对.c,我们用这个程序的变化过程来详细解释gcc的各编译阶段。(文献[5]中详细地进行了解释,下面为了方便理解,我们将[5]中各概念再次给出)
(1)预处理
该阶段编译器主要做加载头文件、宏替换、条件编译等工作,一般用于处理带"#"的语句,如#define xxx xxx。可以使用gcc -E 选项进行查看。在这一步骤中.c文件被预处理输出为.i文件,即
(2)编译
编译阶段主要做语法检查和词法分析,在确认所有指令都符合语法规则之后,将.i文件翻译成等价的中间代码或汇编代码。可以使用gcc -S 选项进行查看。在这一步骤中.i文件被编译输出为.s文件,即
(3)汇编
汇编阶段将.s文件转成二进制目标代码.o文件(并非最终的可执行文件)。在这一步骤中.s文件被汇编输出为.o文件,即
(4)链接
最终的链接阶段,将目标文件、启动代码、库文件链接成可执行文件。在这一步骤中.o文件被链接成最终的可执行文件,即
啊对对对.o --> 啊对对对 //假设最终的可执行文件的名字叫做:啊对对对(当然也可以不叫这个名字)
综上所述,一个c源文件最终生成可执行文件要经过如下的过程:
表1 gcc各阶段以及文件类型
gcc阶段 | 文件类型(源文件为.c) |
预处理 | .c → .i |
编译 | .i →.s |
汇编 | .s →.o |
链接 | .o →可执行文件 |
2.2 gcc常用参数解释
在使用gcc对c代码进行编译的过程中,通常会有两个参数-E、-S、-c和-o,这四个个参数分别表示如下:
-E 预处理后即停止,不进行编译、汇编和链接动作,即生成.i文件即停止
-S 预处理和编译后即停止,不进行汇编和链接动作,即生成.s文件即停止
-c 预处理、编译并汇编生成中间同名二进制目标文件,即生成.o文件即停止
-o 自定义生成的可执行文件或者其他输出文件(.s文件,.o文件)的的名字,不加-o会默认生成a.out(说简单点,就是给文件换名字的)
举两个栗子。第一个栗子:假设只有一个源文件名为happy.c,使用gcc对其编译通常可以用以下三种方法:
/* 方法1 */
1 gcc -c happy.c //由于有参数-c,该语句生成了同名的.o文件并没有进行链接,因此需要下一步生成可执行文件
2 gcc -o HAPPY happy.o //由happy.o生成名为HAPPY的可执行文件
3 ./HAPPY //执行HAPPY
或
2 gcc happy.o //若不加-o,则生成a.out可执行文件
3 ./a.out //执行a.out
/* 方法2 */
1 gcc -c happy.c -o HAPPY.o //将二进制目标文件命名为HAPPY.o
2 gcc -o HAPPY HAPPY.o //将.o文件链接成可执行文件HAPPY
3 ./HAPPY //执行HAPPY
或
2 gcc HAPPY.o //若不加-o,则生成a.out可执行文件
3 ./a.out //执行a.out
/* 方法3 */
1 gcc happy.c //由于没有任何参数,gcc直接生成可执行文件a.out(默认文件名)
2 ./a.out //执行a.out
或
1 gcc happy.c -o HAPPY //由于加了参数-o,因此可执行文件名为HAPPY
2 ./HAPPY //执行HAPPY
第二个栗子,假设有三个c源文件: happy.c、little_happy.c和very_happy.c,我们可以通过以下命令对这三个源文件进行编译:
1 gcc happy.c little_happy.c very_happy.c -o HAPPY //将三个源文件编译生成名为HAPPY的可执行文件
2 ./HAPPY //执行HAPPY
//当然也可以加-c等参数,和第一个栗子的过程类似
可以看到,当源文件增多时,命令行的语句也随之增多。对于大型项目,往往有成千上万个文件(并且不同源文件的路径还不相同),如果还是用上面的命令行进行编译的话,将会非常麻烦低效。比如当一万个源文件中有一个被修改时,命令行将会把全部一万个源文件都编译一遍,这显然是一种低效的方案。并且命令行的输入字符数有上限,因此很有可能会使得无法对整个项目进行编译。
而makefile文件就很好地解决了上面这个问题。makefile可以保存命令行来化简编译工作,并且可以识别出哪些文件是修改过的,哪些是没变化的。它只去编译被修改过的文件,从而节省了时间,提高了效率。下面我们介绍makefile的基本语法。
三、makefile基本语法
首先介绍makefile的基本语法,然后以一个栗子进行说明。
3.1 makefile基本语法
1. make命令
make xxx : 执行xxx
make会在当前目录下寻找Makefile或者makefile文件,若查找到则会查找文件中的第一个.o文件,若.o文件不存在则根据依赖关系查找.s文件,若.s文件不存在,则根据依赖关系查找.i文件,若还不存在,则根据依赖关系查找.c文件。由于.c文件一定存在,于是生成一个.o文件再去执行。(依赖关系:我的理解是文件a需要文件b和文件c产生,所以a与b和c之间的关系为依赖关系)
2. makefile文件格式
目标: 依赖
[tab]命令1
[tab]命令2
[tab]命令3
...
目标:make命令所要构建的对象,可以是文件名也可以是某个操作名。Makefile 文件默认只生成第一个目标文件即完成编译。
在实际中,可能会存在这样一种情况,就是makefile中的目标名和相同目录下的某个文件名一样,这样在进行make的时候,makefile便不会执行目标内相应的指令。为了避免这种情况,我们引入伪目标(PHONY,中文意思是假的东西)。这样make命令便不再管目录中拥有相同名称的文件而是会执行makefile中目标的指令。
依赖:查看目标文件由哪些文件生成,通常是一组用空格隔开的文件名。如果找不到所依赖的生成文件,则会在其他位置寻找是否可以产生依赖文件。举一个简单的栗子:
all: output.out
output.out: file1.o file2.o
gcc file1.o file2.o -o output.out
file1.o: file1.c
gcc -c file1.c -o file1.o
file2.o: file2.c
gcc -c file2.c -o file2.o
这是一个由file1.c和file2.c两个源文件生成可执行文件output.out的简单makefile文件。当执行该文件的时候,首先会寻找output.out文件,然后发现该文件依赖file1.o和file2.o两个文件,而这两个文件此时还没有生成,所以继续向下寻找。随后发现这两个.o文件依赖于file1.c和file2.c这两个.c文件,于是通过gcc生成这俩.o文件,随后向上生成output.out文件。
命令:通过执行该命令,由依赖文件生成目标文件。需要注意的是每个命令前面需要有一个tab!
3. makefile语法
(1)注释:每行以#开头的为注释
(2)echo:显示输出的内容
echo: 会在shell中显示echo这条命令和后面要输出的内容
@echo: 不会显示echo这条命令,只会显示后面要输出的内容
例如:
echo “happy” 输出为:echo "happy" happy
@echo "happy" 输出为:happy
(3)通配符(*、?和[...]):指定一组符合条件的文件。
*:任意0个或多个字符
?:任意一个字符
[...]:方括号内任意一个字符
(4)匹配符:%,可以将大量相同的文件用一条规则构建。
假设有两个源文件file1.c和file2.c,现在需要将它们编译成.o文件,以下两种目标的构建是等价的:
%.o: %.c
//等价于
file1.o: file1.c
file2.o: file2.c
(5)变量与赋值
自定义变量,使用x=赋值,其中x=表示=、:=、?=或+=。调用变量时,将变量名放在$()中。下面举一个栗子来说明这些符号之间的区别:
#赋值符号“=”
#递归赋值,也就是说会寻找赋值变量的值,也是默认的赋值方式
var0=$(var1)
var1="MAKEFILE"
all:
echo $(var0)
输出:MAKEFILE
#赋值符号“:=”
#直接赋值,不会递归,也就是不会向下寻找
var0:=$(var1)
var1="MAKEFILE"
all:
echo $(var0)
输出:空字符串
#赋值符号“?=”
#若变量未初始化,则赋值
var0="makemake"
var0?="MAKEFILE"
var1?="MAKEFILE"
all:
echo $(var0) and $(var1)
输出:makemake and MAKEFILE
#赋值符号“+=”
#将字串加到现有字符串的末尾
var0="MAKE"
var0+="FILE"
all:
echo $(var0)
输出:MAKEFILE
(6)自动变量$@、$^、$<、$?、$(@D)和$(@F)
$@:目标的名字
$^:构造所需文件列表所有所有文件的名字
$<:构造所需文件列表的第一个文件的名字
$?:构造所需文件列表中更新过的文件
$(@D):表示$@的目标名
$(@F):表示$@的文件名
再举个栗子:
HAPPY: happy.c
gcc -o $@ $<
#$@表示HAPPY,$<表示happy.c
HAPPY: happy.c very_happy.c
gcc -o $@ $^
#$^表示happy.c very_happy.c
(7)函数
函数调用的语法如下:
$(func args): 其中,args参数之间以","隔开,func和args之间以" "隔开
$(subst <from>,<to>,<text>):字符串替换,将字符串<text>中的<from>转换成<to>,返回替换后的字符串
举个栗子:
$(subst .c,.o, happy.c very_happy.c)
表示用“.o”替换字符串“happy.c very_happy.c”中的字符“.c”,得到“happy.o very_happy.o”
$(patsubst <pattern>,<replacement>,<text>):模式字符串替换,查找字符串<text>中符合模式<pattern>的单词,并替换成<replacement>
$(strip <string>):去空格,去掉<string>中开头和结尾的空白符,返回去掉空格的字符串
$(findstring <find>,<in>):查找字符串,在字符串<in>中查找<find>字符串,若找到则返回<find>,否则返回空字符串
$(filter <pattern...>,<text>):过滤,以<pattern>模式过滤<text>字符串中的单词,保留符合模式的单词,返回符合模式的字符串;可有多个模式,模式之间以" "隔开
$(wildcard 寻找的文件):在系统中寻找文件。例如:$(wildcard *.c) 就等于找到系统中所有后缀为.c的文件(“*”表示任意个字符),返回以空格隔开的一整行字符
$(basename <names>):取去掉后缀的文件名,也就是前缀,比如$(basename happy.c),就可以获得happy(basename: 中文意思是基本的名称)
$(suffix <names...>):取后缀,从文件名序列<names>中获取并返回各文件的后缀序列,例如$(suffix happy.o),就可以获得后缀“.o”(suffix: 中文意思是后缀,所以带这个单词的函数都和后缀有关)
其他未列出的函数可以自行百度。
3.2 实战栗子
该makefile文件为侧信道形式化验证工具SILVER中的makefile,网址为:GitHub - Chair-for-Security-Engineering/SILVER: SILVER - Statistical Independence and Leakage Verification
本栗子的目的是为了理解该makefile文件是如何运行的。
可以看到在1-26行,均为变量的赋值,各赋值符号以及通配符的含义在3.1小节中详细地给出
27-52行可以看到,构建了all、build、debug、release和clean五个目标,并在第38行将它们五个都设置为伪目标。
按照Build Instructions步骤,最后一步为make release。当我们输入该指令并按下回车后,它会首先在当前目录下寻找makefile文件。找到后,寻找release目标。找到后发现其有两部分构成。在给CXXFLAGS拼接上相应字符串后,开始寻找目标all。
在第28行找到了all,发现其有两个依赖:build和$(BIN_DIR)/$(TARGET)。对于第一个依赖build,在第40行找到目标build,并按照命令创建两个目录。对于第二个依赖$(BIN_DIR)/$(TARGET),在第34行找到目标,发现其依赖$(OBJECTS),在第25行找到对应的赋值,发现其中缺少$(OBJ_DIR)/%.o。在第30行找到对应的目标$(OBJ_DIR)/%.o,按照第31和第32行的命令创建目录并编译(不链接,因为是-c),生成目标.o文件。
此时目标$(BIN_DIR)/$(TARGET)中的依赖$(OBJECTS)构建完成,开始执行35和36行的命令,创建目录并完成最终的编译。此时目标all构建完毕,make release命令结束。
参考文献:
[1]Makefile 语法入门_阿飞__的博客-CSDN博客_makefile语法
[2]makefile中=、:=和+=的区别 - _小百 - 博客园
[3]Makefile中的匹配符%_忘尘~的博客-CSDN博客_makefile中%
[4]一文详解CMake编译工具与项目构建
[5]gcc -c -o编译过程_奔跑的码农的博客-CSDN博客
[6]makefile脚本基本语法_月亮+六便士的博客-CSDN博客_简述makefile基本语法格式
写在最后面:疫情赶紧结束吧。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)