Linux入门指南:基础开发工具---make/Makefile


目录

一.make和Makefile的基本概念

make工具的作用和背景

Makefile的结构和作用

为什么需要make/Makefile

二.Makefile的基本结构与语法

依赖关系与依赖方法

一个简单的Makefile实例

三.make的工作原理

make是如何工作的?

文件的有关时间

如何查看文件的有关时间

手动更新文件时间

判断文件新旧

推导栈的形成与推导过程

make clean

四.常用的Makefile规则和变量

特殊规则:.PHONY

自定义变量

自动变量

五.多文件项目的Makefile编写

指定环境

Makefile编写

六.总结


一.make和Makefile的基本概念

make工具的作用和背景

        在软件开发中,特别是使用 C/C++ 这类需要编译的语言时,一个项目通常由多个源文件组成,手动逐个编译这些文件不仅繁琐、效率低下,而且在只修改了少数几个文件后重新编译整个项目也非常耗时,make工具就是为了解决这个问题而诞生的。它是一个自动化构建工具,能够根据Makefile文件中定义的规则,判断项目中哪些部分需要重新编译,并执行相应的命令来完成编译和链接工作


Makefile的结构和作用

        Makefile是一个文本文件,它告诉make工具如何编译和链接程序。它包含了一系列的规则 (Rules),每个规则说明了如何从一个或多个依赖文件来构建目标文件


为什么需要make/Makefile

  1. 自动化:只需一个make命令,即可完成复杂的编译流程

  2. 效率:基于时间戳的依赖检查,只编译必要的文件,节省大量时间

  3. 可维护性:将编译指令、链接选项、依赖关系集中管理,项目结构清晰

  4. 可移植性:一个好的Makefile可以在不同的 Unix-like 系统上工作


二.Makefile的基本结构与语法

依赖关系与依赖方法

  • 依赖关系
test:test.c

        “:”左边的为目标文件,冒号右边的是依赖文件列表,即可以有多个文件,要得到可执行文件test,它的生成依赖于源文件test.c,这就是文件之间的依赖关系,特别注意:依赖关系必须存在,但依赖文件列表可以为空

  • 依赖方法
test:test.c
    gcc test -o test.c   # 此行为依赖方法

        依赖方法则是依赖文件列表要执行怎样的操作才能得到目标文件,这里的test.c要通过gcc进行编译才能得到可执行文件test,即相当于告诉编译器我们要进行的操作,要注意的是在写依赖方法前要加一个Tab,并且依赖方法可以是任意的shell命令


一个简单的Makefile实例

使用方法:

  • make:编译生成可执行文件
  • make clean:清理中间文件和可执行文件

三.make的工作原理

make是如何工作的?


文件的有关时间

  • 在二次输入make命令后,系统会提示当前构建的目标文件已是最新的状态而无法执行

  • 而源文件发生更改后,才会重新编译

  • 这本质上是因为gcc无法二次编译老代码,但在源文件发生更改后,旧代码变为新代码,从而能被重新编译,这归结于源文件和可执行文件谁的修改时间谁更新的问题,同时也表明文件的新旧取决于文件的有关时间                        

如何查看文件的有关时间

stat test    # 查看文件的状态与时间

  • Acess:文件最近一次被访问的时间,查看文件内容、修改文件内容,都属于访问文件
  • Modify:最近一次新建文件或修改文件内容的时间
  • Change:最近一次修改文件属性的时间

        由于文件 = 文件内容 + 文件属性,若修改文件内容,首先需要访问文件Mod和Ace时间都会更新,同时文件的大小也会因为内容而改变,而文件的大小与Mod时间都属于文件的属性,所以Change时间也会更新,因此修改文件内容会导致三个时间都进行更新,进一步验证了这三个时间是相互关联的

        stat也属于访问文件,但Acess为什么没有更新?这是因为对文件的各种操作,都会导致Access时间改变,从而增加访问磁盘的次数,导致OS整体效率降低,所以在现在的Linux中,对Access的更新策略进行了修改,维护了一个计数器,会根据Modify和Access的更新达到一定次数的时候,才会更新Access,以此来提高系统的运行效率


手动更新文件时间

touch test.c   # 更新文件的所有时间


判断文件新旧

  • 判断文件的新旧是根据文件的Mod时间判定的        

最后通过时间轴再来梳理一下gcc/make二次编译的问题:


推导栈的形成与推导过程

下面是一个进行完整编译过程的Makefile:

test:test.o                                                                                                                                                                     
    gcc test.o -o test 
test.o:test.s
    gcc -c test.s -o test.o
test.s:test.i
    gcc -S test.i -o test.s
test.i:test.c
    gcc -E test.c -o test.i
 
.PHONY:clean
clean:
    rm -rf test test.i test.s test.o

        从上图可以看出,输入make指令以后,它实际执行makefile文件中指令的顺序,和我们写在makefile文件中的指令顺序是反的

        这是因为,要得到test必须要依赖test.o,但是当前目录下没有test.o这个文件,但是makefile文件中有得到test.o的方法,但是,要得到test.o又必须依赖test.s,但当前目录下依旧没有test.s,以此类推,要先得到test.i文件,才能得到test.s文件,然后才能得到test.o文件,最后才能得到test,所以实际指令的执行顺序,于我们在makefile文件中写入的,是反过来的

        实际上保存这些依赖关系的是一种栈式的结构,即为推导栈

        这就是整个make的依赖性make会一层又一层地去找文件的依赖关系,直到最终编译出第⼀个目标文件,上面的Makefile只是为了演示其自动化推导的能力,实际运用中可以省略中间过程的依赖关系和依赖方法

test:test.c                                                                                                                                                                     
    gcc test.c -o test 

.PHONY:clean
clean:
    rm -rf test

make clean

clean:           # 依赖列表为空
    rm -rf test

        make命令的后面可以跟目标名,后面跟哪个目标就解析谁的依赖关系和依赖方法,所以make clean只是创建了一个没有依赖列表的clean目标,利用了make的自动化推导能力,让其选择性执行rm命令,在构建工程的角度,看起来就是清理项目,本质就是删除不需要的临时文件

        make后面如果不跟目标名,默认只会按顺序推导第一个依赖关系对应的推导链


四.常用的Makefile规则和变量

特殊规则:.PHONY

.PHONY:clean
  •  .PHONY用于修饰目标文件是一个伪目标。伪目标并不代表一个实际的文件名,它只是一个标签,代表一系列需要总是被执行的命令
  • .PHONY能够让gcc或命令忽略Mod对比时间新旧,从而使命令具有总是被执行的特性
  • 为什么需要它?如果你当前目录下恰好有一个文件叫做 clean,那么当你执行 make clean 时,make 会认为 clean 文件已经存在且没有依赖项需要更新,从而不会执行 rm 命令。用 .PHONY 声明后,make 就不会去检查文件是否存在,而是直接执行命令

        所以我们通常会用.PHONY用来修饰clean,让make clean总是能够被执行,从而能有效地进行清理工程


自定义变量

  • 定义变量(赋值)
BIN=test     # 定义目标文件
SRC=test.c   # 定义源文件列表
CC=gcc       # 定义一个编译器
RM=rm -rf    # 定义一个命令及其选项
  • 使用变量(展开)

可以使用 $(value_name) 来获取变量的值:

BIN=test     # 定义目标文件
SRC=test.c   # 定义源文件列表
CC=gcc       # 定义一个编译器
RM=rm -rf    # 定义一个命令及其选项                                                                                                                                             
 
$(BIN):$(SRC)             
    $(CC) $(SRC) -o $(BIN)
           
.PHONY:clean
clean:          
    $(RM) $(BIN)

在上面的依赖中,make 会将这些变量展开为:

test:test.c
    gcc test.c -o test
 
.PHONY:clean
clean:
    rm -rf test
  • 为什么需要自定义变量
  1. 避免重复:比如,如果你在 Makefile 里写了十次gcc -o,有一天你想把编译器换成clang或者想增加一个-g调试选项,你就需要修改十个地方,很容易出错。使用变量后,你只需修改一个地方

  2. 集中配置:通常会把所有重要的变量定义在 Makefile 的顶部,形成一个清晰的“配置区域”。任何人一看就知道这个项目用什么编译、编译什么文件、输出是什么

  3. 易于覆盖:可以在调用make的命令行上轻松覆盖这些变量的值,而无需修改 Makefile 本身,这非常灵活


自动变量

        这类变量在依赖方法的命令部分被求值,在每个依赖中都可以有不同的值,其优势体现于在目标或依赖文件名长,数量多的情景

  • $@:当前依赖中的目标文件名

  • $<:当前依赖中的第一个依赖文件名,常与通配符%搭配使用

  • $^:当前依赖中的所有依赖文件列表,以空格分隔

  • $?:比目标文件更新的所有依赖文件列表

使用自动变量重写之前的简单示例:

test:test.c
    $(CC) $^ -o $@ 
  • 上面的依赖方法等价于 gcc test.c -o test 命令 

五.多文件项目的Makefile编写

指定环境

        现实中的项目通常包含多个 .c 文件和 .h 文件。我们需要先将每个 .c 文件编译成 .o 目标文件,最后再将所有 .o 文件链接成最终的可执行文件

  • 模拟多文件环境


Makefile编写

# 配置列表                                                                                                                                                                          
BIN=code.exe     
#SRC=$(shell ls *.c)             # 采⽤shell命令⾏⽅式,获取当前所有.c⽂件名
SRC=$(wildcard *.c)              # 使用wildcard函数,获取当前目录所有.c文件    
OBJ=$(SRC:.c=.o)                 # 将SRC中的所有同名.c替换为.o,形成目标文件列表    
CC=gcc     
RM=rm -rf    
    
$(BIN):$(OBJ)    
    @$(CC) $^ -o $@              # @:不回显依赖方法    
    @echo "linking...$^ to $@"   # 向显示器输出链接过程    
    
# 告诉 make 如何从任意 .c 文件编译出同名的 .o 文件    
%.o:%.c                          # %为通配符,展开当前目录下的.c文件,同时展开同名.o    
    @$(CC) -c $< -o $@    
    @echo "compling...$< to $@"    
    
.PHONY:clean    
clean:    
    @echo "清理工程..."    
    @$(RM) $(BIN) $(OBJ)         # 删除.o/.exe文件    
    @echo "清理完成..."                                             
                                                                    
.PHONY:test                      # 测试.c/.o文件完整性              
test:                                                               
    @echo $(SRC)                                                    
    @echo $(OBJ)    
  • 运行效果


六.总结

        本期的 Linux 开发工具指南介绍了 make 工具和 Makefile 文件。我们了解了 make 如何通过 Makefile 中定义的依赖关系依赖方法来自动化管理项目的编译构建流程。其核心工作原理在于比较目标文件和依赖文件之间的修改时间,以此决定是否需要重新执行命令,从而避免重复编译,极大提升了开发效率。我们还学习了如何编写 Makefile,包括使用自定义变量自动变量来简化脚本,以及如何为多文件项目构建高效的自动化编译规则。掌握 make/Makefile 是迈向高效开发的重要一步。下期,我们将介绍另一个至关重要的开发工具:Git,学习如何进行版本控制与团队协作


评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值