《Linux系统编程》Linux基础开发工具 (二):详解自动化构建 make / Makefile

🔥小叶-duck个人主页

❄️个人专栏《Data-Structure-Learning》《C++入门到进阶&自我学习过程记录》
《Linux操作系统从入门到实践》《Qt从入门到实践》
《算法题讲解指南》--优选算法
《算法题讲解指南》--递归、搜索与回溯算法
《算法题讲解指南》--动态规划算法

未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游


目录

前言

[一、Makefile 核心认知:什么是 Makefile?](#一、Makefile 核心认知:什么是 Makefile?)

[1.1 背景介绍](#1.1 背景介绍)

[1.2 核心作用](#1.2 核心作用)

[1.3 核心概念](#1.3 核心概念)

[二、make 基本使用:实现最简单的 Makefile](#二、make 基本使用:实现最简单的 Makefile)

[2.1 源文件code.c](#2.1 源文件code.c)

[2.2 基础 Makefile](#2.2 基础 Makefile)

[2.3 实际操作演示](#2.3 实际操作演示)

[三、Makefile 语法规则:深入理解依赖与命令](#三、Makefile 语法规则:深入理解依赖与命令)

[3.1 基本语法格式](#3.1 基本语法格式)

[3.2 依赖关系与推导](#3.2 依赖关系与推导)

编译流程解析(从下往上执行):

[3.3 伪目标(.PHONY)](#3.3 伪目标(.PHONY))

四、进阶用法:变量与函数(工程化必备)

[4.1 变量定义与使用](#4.1 变量定义与使用)

[4.2 常用函数(简化文件列表)](#4.2 常用函数(简化文件列表))

[4.3 自动变量(简化命令书写)](#4.3 自动变量(简化命令书写))

[4.4 简化 Makefile/makefile(多源文件适配)](#4.4 简化 Makefile/makefile(多源文件适配))

结束语


前言

在 Linux C/C++ 开发过程中,随着项目源文件不断增多,手动输入 gcc 命令进行编译会变得异常繁琐且极易出错。我们不仅需要理清各个文件之间的依赖关系,还要严格控制编译顺序与编译参数,效率非常低下。Makefile 的核心价值正是自动化构建:只需编写一次构建规则,后续仅需执行 make 命令,即可自动完成编译、链接、清理等整套流程,大幅提升开发效率与项目可维护性。本文将从 Makefile 的基本原理讲起,逐步拆解语法规则、依赖管理、伪目标、变量与函数等核心知识点,并通过工程化实战案例带你完整上手,让你从 "手动编译" 快速过渡到 "自动化构建"

一、Makefile 核心认知:什么是 Makefile?

1.1 背景介绍

  • 会不会写makefile,从侧面说明了一个人是否具备完成大型工程的能力
  • 一个工程中的源文件不计其数,其按类型,功能,模块分别放在若干个目录中,makefile 定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于更复杂的操作
  • makefile 带来的好处就是 -- "自动化编译",一旦写好,只需要一个 make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
  • make 是一个命令工具,是一个解释 makefile 中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi 的 make,Visual C++ 的 namke,Linux下GNU的make。可见,makefile 都成为了一种在工厂方面的编译方法。
  • make是一条命令,makefile是一个文件,两个搭配使用,完成项目自动化构建。

1.2 核心作用

  • 定义源文件之间的依赖关系(如可执行文件依赖目标文件,目标文件依赖源文件);
  • 指定编译规则(如如何将.c文件编译为.o文件,如何链接为可执行文件);
  • 支持增量编译(只重新编译修改过的文件,减少重复工作);
  • 提供项目清理功能(一键删除编译产物,方便重新构建)。

1.3 核心概念

  • make:解释 Makefile 规则的命令工具,默认查找当前目录下名为Makefile或makefile的文件;
  • 目标(target):要生成的文件或要执行的命令(如可执行文件、清理操作);
  • 依赖(prerequisites):生成目标所需要的文件或条件(如生成myproc依赖myproc.c);
  • 命令(command):生成目标的具体操作(如gcc -o myproc myproc.c),必须以 Tab 键开头。

二、make 基本使用:实现最简单的 Makefile

假设项目只有一个源文件 code.c,要生成可执行文件 code,并支持清理编译产物。

2.1 源文件code.c

cpp 复制代码
#include<stdio.h>
 
int main()
{
	printf("hello Makefile!");
	return 0;
}

2.2 基础 Makefile

bash 复制代码
# 目标:依赖
code: code.c
	# 命令(必须Tab开头):将code.c编译为可执行文件code
	gcc -o code code.c

# 伪目标:清理编译产物
.PHONY: clean
clean:
	# -f表示强制删除,忽略不存在的文件
	rm -f code

2.3 实际操作演示

bash 复制代码
[admin@iZbp12ear9ufvimc78fddkZ mk]$ make
gcc code.c -o code
[admin@iZbp12ear9ufvimc78fddkZ mk]$ ll
total 44
-rwxrwxr-x 1 admin admin 8360 May 19 20:17 code
-rw-rw-r-- 1 admin admin   76 May 19 20:17 code.c
-rw-rw-r-- 1 admin admin 1750 May 19 20:15 makefile
[admin@iZbp12ear9ufvimc78fddkZ mk]$ ./code
hello Makefile!
[admin@iZbp12ear9ufvimc78fddkZ mk]$ make clean
rm -f code
[admin@iZbp12ear9ufvimc78fddkZ mk]$ ll
total 32
-rw-rw-r-- 1 admin admin   76 May 19 20:17 code.c
-rw-rw-r-- 1 admin admin 1750 May 19 20:15 makefile

核心用法

  • 编译项目: 在Makefile 所在目录执行make,自动查找第一个目标(code),检查依赖是否更新,执行编译命令;
  • 清理项目: 执行make clean,执行 clean 目标下的命令,删除可执行文件;
  • 增量编译: 修改 code.c 后再次执行 make,只会重新编译修改后的文件,而非全部重编

三、Makefile 语法规则:深入理解依赖与命令

3.1 基本语法格式

bash 复制代码
目标(target): 依赖(prerequisites)
	命令1
	命令2
	...
  • 目标可以是可执行文件、目标文件(.o)、伪目标(如clean);
  • 依赖可以是源文件、目标文件、其他目标;
  • 命令必须以Tab 键 开头(不能用空格,否则make会报错);
  • 注释以#开头,单行有效。

3.2 依赖关系与推导

make命令内部维护了一个栈结构,因为栈是先进后出后进先出的,文件还不存在------入栈,找到文件------出栈。

参考图示:(Makefile 会自动推导目标文件的依赖)

bash 复制代码
myproc:myproc.o 
	 gcc myproc.o -o myproc
myproc.o:myproc.s 
	 gcc -c myproc.s -o myproc.o
myproc.s:myproc.i 
	 gcc -S myproc.i -o myproc.s
myproc.i:myproc.c 
	gcc -E myproc.c -o myproc.i
 
.PHONY:clean 
clean: 
	 rm -f *.i *.s *.o myproc

编译一下:

bash 复制代码
$ make
gcc -E myproc.c -o myproc.i
gcc -S myproc.i -o myproc.s
gcc -c myproc.s -o myproc.o
gcc myproc.o -o myproc
编译流程解析(从下往上执行):
阶段 命令 输入文件 输出文件 作用说明
预处理 gcc -E code.c -o code.i code.c code.i 处理宏定义、头文件包含等预处理指令
编译 gcc -S code.i -o code.s code.i code.s 将 C 代码转换为汇编语言
汇编 gcc -c code.s -o code.o code.s code.o 将汇编代码转换为二进制目标文件
链接 gcc code.o -o code code.o code 链接目标文件生成可执行程序

make是如何工作的,在默认的方式下,也就是我们只输入make命令。那么:

  1. make 会在当前目录下找名字叫 "Makefile" 或 "makefile" 的文件。
  2. 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到 myproc 这个文件,并把这个文件作为最终的目标文件。
  3. 如果 myproc 文件不存在,或是 myproc 所依赖的后面的 myproc.o 文件的文件修改时间要比 myproc 这个文件新(可以用 touch 测试),那么,他就会执行后面所定义的命令来生成 myproc 这个文件。
  4. 如果 myproc 所依赖的 myproc.o 文件不存在,那么 make 会在当前文件中找目标为 myproc.o 文件的依赖性,如果找到则再根据那一个规则生成 myproc.o 文件。(这有点像一个堆栈的过程)
  5. 当然,你的 C 文件和 H 文件是存在的啦,于是 make 会生成 myproc.o 文件,然后再用 myproc.o 文件声明 make 的终极任务,也就是执行文件 hello 了。
  6. 这就是整个 make 的依赖性,make 会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。
  7. 在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么 make 就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make 根本不理。

但是我们一般是用不到这么多步的,最佳实践如下:

bash 复制代码
# 最终目标:可执行文件code.exe,依赖code.o
code.exe: code.o
	gcc code.o -o code.exe

# 目标文件code.o,依赖code.c
code.o: code.c
	gcc -c code.c -o code.o

# 伪目标clean
.PHONY: clean
clean:
	rm -f code.exe code.o

3.3 伪目标(.PHONY)

  • 伪目标不是实际文件 ,而是一个**"命令标签"**(如clean);
  • 作用:避免项目中存在与伪目标同名的文件,导致 make 误判为 "目标已存在,无需执行";
  • 语法.PHONY: 伪目标名 ,例如 .PHONY: clean,确保 make clean总是执行命令

图示理解 :(什么叫总是被执行?)

结论:.PHONY:让make忽略源文件和可执行目标文件的M时间对比

出现了这样的崩溃是什么意思(补充内容)?

这是Vim编辑器的交换文件(Swap File) 提示信息

什么是交换文件?

Vim在编辑文件时会自动创建临时交换文件(.swp结尾),用于崩溃时恢复数据。截图中显示的交换文件路径为:

bash 复制代码
~/.local/share/nvim/swap//%home%Alice%Ludy%Makefile.swp

为什么会出现这个提示?

该交换文件由用户在 11月25日23:46 创建,对应原文件是 ~Alice/Ludy/Makefile

关键矛盾: 现在打开的 Makefile 修改时间是 11月26日15:43(比交换文件更新),系统检测到文件状态不一致

两种可能原因:

(1)另一程序正在编辑 :可能有其他Vim窗口或其他用户同时打开了该文件,直接编辑可能导致内容冲突;

(2)上次编辑崩溃 :之前的Vim会话异常退出(如强制关闭、断电),交换文件保留了未保存的修改

操作选项说明:

选项 含义 建议场景
(O)pen Read-Only 只读打开,不修改文件 仅查看内容,避免冲突
(E)dit anyway 强制编辑,忽略交换文件 确认无人使用且不需要恢复时
(R)ecover 从交换文件恢复数据 需要找回上次未保存的修改
(D)elete it 删除交换文件 确认原文件已更新且无恢复需求
(Q)uit / (A)bort 退出 / 中止操作 暂时不处理该文件

推荐操作步骤:

先确认是否有其他程序正在编辑 Makefile(如询问用户);

若文件已手动保存最新版本,直接按 D 删除交换文件,下次打开不再提示;

若需恢复上次崩溃前的内容,按 R 恢复后,记得手动删除交换文件。

注意事项:

交换文件是临时文件,正常退出Vim时会自动删除;

若频繁出现此提示,可能是Vim配置或文件权限问题,可检查 ~/.local/share/nvim/swap 目录权限

四、进阶用法:变量与函数(工程化必备)

当项目源文件增多时,手动写每个文件的依赖和命令会很繁琐。Makefile 支持变量和函数,可简化配置、提高复用性。

4.1 变量定义与使用

变量用于存储重复出现的内容(如编译器 、编译参数、文件列表)
语法:变量名=值,使用时 $(变量名)。

示例

bash 复制代码
# 定义变量:编译器、可执行文件名、目标文件列表、清理命令
CC = gcc          # 编译器
BIN = myproc      # 可执行文件名
OBJ = myproc.o    # 目标文件列表
RM = rm -f        # 清理命令
CFLAGS = -Wall -g # 编译参数(-Wall显示所有警告,-g生成调试信息)

# 最终目标:依赖OBJ变量
$(BIN): $(OBJ)
	$(CC) $(CFLAGS) -o $(BIN) $(OBJ)

# 目标文件依赖源文件(可省略,make自动推导)
myproc.o: myproc.c

# 清理伪目标
.PHONY: clean
clean:
	$(RM) $(BIN) $(OBJ)

4.2 常用函数(简化文件列表)

Makefile 提供内置函数 ,可自动获取文件列表,无需手动罗列。

(1)shell命令行/wildcard函数:获取指定模式的文件

cpp 复制代码
# 获取当前目录下所有.c文件,存入SRC变量
SRC = $(shell ls *.c)
SRC = $(wildcard *.c)

(2)替换文件后缀

cpp 复制代码
# 将SRC中的.c文件替换为.o文件,存入OBJ变量
Obj = $(SRC:.c=.o)

4.3 自动变量(简化命令书写)

Makefile 提供自动变量,替代命令中重复出现的目标和依赖,简化书写:

自动变量 含义 示例
$@ 当前目标文件名 (CC) -o @ $^
$^ 所有不重复的依赖文件 (CC) -o @ $^
$< 第一个依赖文件名 (CC) -c < -o $@

4.4 简化 Makefile/makefile(多源文件适配)

假设项目有多个源文件,Makefile 可自动适配,简化后的 Makefile 如下所示:

bash 复制代码
BIN=myproc.exe # 定义变量                                                                                                                                                                    
CC=gcc

#OBJ=code.o
OBJ=$(SRC:.c=.o)

#SRC=code.c
SRC=$(shell ls *.c)      # 采用shell命令行方式,获取当前所有.c文件名
#或者SRC=$(wildcard *.c) # 或者使用wildcard 函数,获取当前所有.c文件名

LFLAGS=-o                # 链接选项
FLAGS=-c                 # 编译选项
RM=rm -f

$(BIN):$(OBJ)
    @$(CC) $^ $(LFLAGS) $@   # $@:代表目标文件名。 $^: 代表依赖文件列表
    @echo "linking $^ to $@" # @:不回显命令
%.o:%.c                      # %.c 展开当前目录下所有的.c。 %.o: 同时展开同名.o
    @$(CC) $(FLAGS) $<       # $<: 对展开的依赖.c文件,一个一个的交给$(CC) (gcc)    
    @echo "compling $< to $@"    
    
.PHONY:    
clean:    
    @$(RM) $(OBJ) $(BIN)    
    @echo "remove $(OBJ) $(BIN)"    
    
.PHONY:    
test:    
    @echo $(SRC)    
    @echo $(OBJ)    

结束语

本文系统讲解了 Makefile 从基础认知、简单实现,到语法规则、伪目标、变量函数、自动变量等进阶工程化用法,完整梳理了依赖推导逻辑与多文件项目的构建思路,帮大家摆脱手动输入 gcc 编译命令的繁琐。Makefile 是 Linux C/C++ 开发中实现自动化构建的核心工具,熟练掌握它能极大提升项目编译、维护与迭代效率。希望对大家学习Linux能有所收获!

相关推荐
cui_ruicheng7 小时前
Linux网络编程(五):基于UDP实现DictServer
linux·服务器·网络·udp
Terasic友晶科技7 小时前
答疑解惑|为DE25-Nano开发板配置Linux kernel时.config文件没有起作用是什么原因?
linux·服务器·fpga开发·linux kernel·de25-nano
爱写代码的小朋友8 小时前
基于多约束遗传算法的中小学排座位优化模型研究
linux·人工智能·算法
DFT计算杂谈8 小时前
VASP新手入门: IVDW 色散修正参数
linux·运维·服务器·python·算法
楼兰公子8 小时前
《深入理解Linux网络技术内幕》配套学习大纲 + 源码Demo + 进阶实战实例
linux·arm开发·学习
青梅橘子皮9 小时前
Linux---开发工具(2)(makefile、进度条、git、gdb)
linux·运维·服务器
剑神一笑9 小时前
Linux less 命令深度解析:从源码看分页器的设计智慧
linux·运维·less
IT大白鼠9 小时前
Dirty Frag漏洞深度分析:Linux内核页缓存污染漏洞的技术原理与安全防护
linux·安全·dirty frag漏洞
李白你好9 小时前
Linux 本地提权工具支持Linux 内核和 Polkit 漏洞
linux·运维·服务器