【嵌入式C语言破壁指南系列—— Makefile:概述、语法与变量解析】
本文从Makefile基础概述到语法规则讲起,拆解如何用Makefile将碎片化编译转化为可维护的自动化流程,让构建系统从负担变为基石。
前言
第七次编译失败时,控制台的报错信息在黑暗中格外刺眼。项目从三个文件扩展到二十多个模块后,手工输入gcc命令已成噩梦:稍不留神漏掉某个依赖,程序便在运行时崩溃。上周生产环境的校验事故,正是因修改头文件后忘记重新编译关联模块——这种隐藏在手工流程中的“定时炸弹”,让开发效率与代码质量同步滑坡。当光标再次移向历史命令时,瞥见邻座屏幕上几行简洁的Makefile规则:
obj := sensor.o network.o data.o
iot_gateway: $(obj)
gcc $^ -o $@ -lpaho-mqtt3cs
%.o: src/%.c
gcc -c $< -Iinclude
它像一张精准的依赖地图,让机器自动追踪文件变更、按需构建。本文将从Makefile基础概述到语法规则讲起,拆解如何用Makefile将碎片化编译转化为可维护的自动化流程,让构建系统从负担变为基石。
一、makefile概述
1. makefile脚本
Makefile 是一个用于自动化构建和管理软件项目的配置文件,通常与 make命令配合使用。它定义了项目的编译规则、依赖关系以及构建步骤,能够高效地处理代码的编译和链接过程。
2. make指令
make包括我们之前所讲的Linux命令本质上都是可执行文件,我们可以通过which make来查找相关指令可执行文件的路径:
当我们的项目足够大包含的源文件足够多的时候,使用gcc命令进行编译需要把所有源文件作为参数加到指令后面,这样命令显得很臃肿,也很麻烦。而make这个可执行文件可以自动寻找makefile、Makefile、GNUmakefile脚本文件中的任何一个,根据里面提前写好的指令来编译文件。
使用makefile进行编译不仅简化了编译指令而且还可以节省编译时间。这是因为我们再使用makefile编译时,系统会首先检查哪些参与编译的文件发生了修改,makefile会只编译这些修改过的文件,没有修改过的文件会直接使用,不会再进行二次编译,相比下gcc命令会对所有文件进行编译。
二、Makefile语法规则
makefile作为脚本文件,有着极其严苛的语法规则,其本质是一组目标-依赖-命令三元组,它的语法规则如下:
目标:依赖文件列表
命令列表
其中目标代表生成的东西,依赖文件表示生成目标所需的东西,下面的命令列表则指示的是如何去做。
例如:
main :main.c fun.c
gcc main.c fun.c -o main
以上就表示通过gcc main.c fun,c -o main指令,依赖main.c、fun.c文件生成可执行文件main。
在上图中,我们可以看到,当我们在命令行执行make时,会执行下面的命令列表中的指令。
注意事项:
- make指令虽然自动寻找makefile、Makefile、GNUmakefile脚本文件中的任何一个,但是可以通过make -f 指定文件名的方式来选择文件(不建议)。
- 执行make命令后默认执行makefile脚本文件中的第一个目标-依赖-命令三元组,而不会执行后面的脚本。如果想要执行其他的指令可以通过make 目标的方式。
我们以下图为例:
上图这个makefile文件有两组目标-依赖-命令三元组,make默认执行第一个目标,当我们想执行其他目标时需要指定目标名,即make 目标名。
此外,当目标的依赖文件列表不存在时,makefile才会继续向下寻找依赖文件是否为其他目标,如果是则会执行目标命令,我们来看以下例子:
main:main.o fun.o
gcc -o main main.o fun.o
main.o:main.c
gcc -c main.c
fun.o:fun.c
gcc -c fun.c
clean:
rm -f main
此时我们的makefile脚本定义了三个目标,而main.o和fun.o为第一个目标的依赖,这时我们执行make看看会发生什么:
如果当前目录下没有依赖所需的.o文件时,makefile会向下寻找,执行main目标所需的依赖项指令,上图可以看到执行的具体指令及顺序;此时删掉main可执行文件,再次运行make命令,可以看到这时只执行了gcc -o main main.o fun.o,这是因为此时.o文件已经存在。
三、Makefile的变量
1.自定义变量
makefile中的变量类似C语言中宏,我们可以将它看作替换。以下为变量的定义和使用:
定义格式:变量名=变量值
取变量值:$变量名
变量名的命名规则
- 变量是大小写敏感的
- 变量一般都在makefile文件的头部定义
- 变量几乎可以在makefile的任何地方使用
例如:
CC=gcc #将gcc赋给CC
EXEC=main #定义可执行文件名
$(EXEC):main.o fun.o
$(CC) main.o fun.o -o $(EXEC) #gcc main.o fun.o -o main
main.o:main.c
$(CC) -c main.c -o main.o #gcc -c main.c -o main.o
fun.o:fun.c
$(CC) -c fun.c -o fun.o #gcc -c fun.c -o fun.o
clean:
rm $(EXEC) *.o
2.系统环境变量
make工具会拷贝系统的环境变量,并将其设置为makefile的变量,在makefile中可以直接读取或修改拷贝后的变量。
在命令行中输入env可以查看系统环境变量:
3.预定义变量
预定义变量是makefile中提取定义好的变量,以下为常见的预定义变量:
| 变量名 | 描述 |
|---|---|
| $@ | 目标名 |
| $< | 依赖文件列表中的第一个文件 |
| $^ | 依赖文件列表中除去重复文件的部分 |
| AR | 归档维护程序的程序名,默认值为 ar |
| ARFLAGS | 归档维护程序的选项 |
| AS | 汇编程序的名称,默认值为 as |
| ASFLAGS | 汇编程序的选项 |
| CC | C编译器的名称,默认值为 cc |
| CFLAGS | C编译器的选项 |
| CPP | C预编译器的名称,默认值为$(CC) -E |
| CPPFLAGS | C预编译的选项 |
| CXX | C++编译器的名称,默认值为 g++ |
| CXXFLAGS | C++编译器的选项 |
在了解了预定义变量后,我们上面的makefile脚本就可以改成如下所示:
CC=gcc #将gcc赋给CC
EXEC=main #定义可执行文件名
$(EXEC):main.o fun.o
$(CC) $^ -o $@ #$(CC) main.o fun.o -o $(EXEC)
main.o:main.c
$(CC) -c $< -o $@ #$(CC) -c main.c -o main.o
fun.o:fun.c
$(CC) -c $< -o $@ #$(CC) -c fun.c -o fun.o
clean:
rm $(EXEC) *.o
此外在makefile中%表示通配符,我们还可以进行如下简化:
CC=gcc #将gcc赋给CC
EXEC=main #定义可执行文件名
OBJ=main.o fun.o
$(EXEC):$(OBJ)
$(CC) $^ -o $@ #$(CC) main.o fun.o -o $(EXEC)
%.o:%.c
$(CC) -c $< -o $@ #$(CC) -c %.c -o %.o
clean:
rm $(EXEC) *.o
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)