Makefile基础使用

📅 2026/7/3 2:59:07 👁️ 阅读次数 📝 编程学习
Makefile基础使用

Makefile是一种用于管理和自动化软件编译过程的文本文件。它通常包含了一系列规则,这些规则描述了如何根据源代码文件生成可执行文件或者其他目标文件。Makefile的核心概念是规则和依赖关系,规则定义了如何生成一个或多个目标文件,而依赖关系则指定了生成目标文件所需要的源文件或其他依赖文件。

1.环境安装

安装 build-essential工具包

sudo apt install -y build-essential

build-essential 是 Ubuntu 等 Debian 系系统的元软件包,集成 gcc、g++、make 等编译必备工具。 安装它可一键配齐 C/C++ 源码编译环境,省去逐个安装编译器与构建工具的繁琐操作。

VSCode插件

2.基础使用

2.1 创造项目环境

hello.c

#include "hello.h" #include <stdio.h> void say_hello() { printf("Hello world!\n"); }

hello.h

#ifndef __HELLO_H__ #define __HELLO_H__ void say_hello(); #endif

main.c

#include "hello.h" int main() { say_hello(); return 0; }

2.2 ​​​​​​​为main.c和hello.c编写基本Makefile

# Makefile内容通常由以下部分组成 # <目标>: <前置依赖> # <需要执行的命令> # 放在第一个的是默认目标 # 目标为编译出main文件,依赖main.o和hello.o文件 # 编译的命令为 gcc -o main hello.o main.o main: hello.o main.o gcc -o main hello.o main.o # main.o目标依赖main.c hello.h # 编译命令为gcc -c main.c main.o: main.c hello.h gcc -c main.c # hello.hello.c hello.h # 编译命令为gcc -c hello.c hello.o: hello.c hello.h gcc -c hello.c # clean目标可以清理编译的临时文件 clean: rm -f main main.o hello.o

规则是Makefile的构建单元,Make工具通过解析这些规则来执行构建过程。

1.规则的基本结构

我们用空行将Makefile的不同规则划分开来。规则有两行构成,第一行为目标和前置依赖,二者通过冒号区分开来,目标在前,前置依赖在后。

# <目标>: <前置依赖> # <需要执行的命令>

2.目标:本条规则需要生成的目标文件名。

3.前置依赖:生成目标文件需要的依赖文件列表。

4.命令:一系列将被Shell执行的命令,用于从前置依赖构建目标。

需要注意的是,Makefile中每个规则的命令必须以一个制表符(tab)开始,而不能是空格。否则会提示“缺失分隔符”。

5.gcc的-c参数不仅可以将汇编代码转换为机器码,还可以直接将C语言源文件转换为机器码,gcc -c main.c就是第二种用法,这里省略了-o main.o。默认情况下,在指定-c参数时,gcc会将与源文件名去掉扩展名再加上后缀.o作为目标文件的名称。

2.3 测试

执行make命令

make clean make ./main

2.4 引入变量

# 定义变量objects objects := hello.o\ main.o # 在目标中引入变量 main: $(objects) gcc -o main $(objects) main.o: main.c hello.h gcc -c main.c hello.o: hello.c hello.h gcc -c hello.c # clean目标中也可以引入变量 clean: rm -f main $(objects)
  • objects为变量名
  • :=的组合相当于C语言中的=,表示赋值
  • :=后面为变量的值
  • \为续行符,表示命令或定义延续到下一行。此处的作用是将hello.o和main.o合并为一行,此处的定义等价于objects := hello.o main.o

$(变量名)表示获取变量的值

2.4 引入make自动推导

更改Makefile:

make可以根据目标自动加入所需的依赖文件和命令。例如main.o目标,会默认将main.c作为依赖加入,同时也可以自动推导出编译main.o的命令,于是我们的Makefile就可以改成以下内容:​​​​​​​

objects := hello.o\ main.o main: $(objects) gcc -o main $(objects) # 利用make的自动推导 clean: rm -f main $(objects)

依赖文件的作用:
要注意的是,虽然这种方式精简Makefile的内容,但是当没有显式声明的依赖文件发生更改时Make无法追踪。只有在Makefile中显式声明依赖的头文件才会被追踪,当它们发生更改时,重新执行make命令,会再次执行相应规则的命令。

2.5 引入伪目标

伪目标并不代表实际的文件名,它们更多的是行为或动作的标识符。伪目标并不生成具体文件。

PHONY目标

.PHONY是Makefile中一个特殊的目标,用于声明其它目标是伪目标。

② 语法:.PHONY:<伪目标名称>

③ 目标为clean的规则没有前置依赖,这是因为它是用来执行清理操作的,并不是要生成名为clean的文件,因此不需要前置依赖。我们可以将clean声明为伪目标。

# 定义变量objects objects := hello.o\ main.o # 在目标中引入变量 main: $(objects) gcc -o main $(objects) main.o: hello.h hello.o: hello.h # 声明伪目标 .PHONY: clean # clean目标中也可以引入变量 clean: rm -f main $(objects)

为什么需要伪目标:

我们发现,执行make clean并没有像我们预想的那样删除文件,而是告诉我们“clean”已是最新。这是因为,make将clean作为普通目标处理,它先检查clean的依赖(不存在),然后发现clean文件已存在且没有依赖更新(因为不存在,自然不需要更新),因此不会执行规则下的命令,并在控制台输出以上内容。显然,这不是我们期望的行为。

将某些不生成目标文件的行为或动作(如清理、安装)声明为伪目标可以确保无条件执行规则下的命令。即便执行make命令时当前目录下存在与目标同名的文件,依然可以得到我们期望的效果。

2.6 忽略错误

rm前面的-告诉make,如果该命令执行失败,不要停止执行剩余的过程,即忽略错误。

# 定义变量objects objects := hello.o\ main.o # 在目标中引入变量 main: $(objects) gcc -o main $(objects) main.o: hello.h hello.o: hello.h # 声明伪目标 .PHONY: clean # clean目标中也可以引入变量 clean: -rm main $(objects)

2.7 目标名和命令中输出文件名的关系

# 定义变量objects objects := hello.o\ main.o # 在目标中引入变量 main: $(objects) gcc -o main123 $(objects) main.o: hello.h hello.o: hello.h # 声明伪目标 .PHONY: clean # clean目标中也可以引入变量 clean: -rm main123 $(objects)

当前目录下不存在名为main的文件,只有名为mian123的文件,可以得出结论:规则中的命令决定了生成目标文件的名称。目标名并不影响目标文件名。

我们发现,make没有提示目标文件已是最新,而是重新执行了gcc -o main123 hello.o main.o。这是因为,make会按照目标名称在当前目录下追踪目标文件,如果不存在与目标同名的文件,会再次执行规则下的命令。

make输出的文件名取决于规则下的命令,而目标名称决定make追踪的目标文件名。如果二者不一致,make就会认为目标文件不存在而不断执行命令。我们应确保命令生成的目标文件名和目标名一致。

​​​​​​​vscode-makefile-term插件

在插件市场搜索并安装vscode-makefile-term插件,完成后,在VScode中打开Makefile文件,可以看到每个target上方都出现了执行按钮。