Makefile快速上手笔记

一、为什么需要 Makefile?

在linux开发过程中,当项目只有一两个 .c 文件时,手动执行 gcc 命令并不麻烦:

bash 复制代码
gcc main.c hello.c -o app

但随着项目规模增长,源文件越来越多,手动编译变得难以维护,特别是对于有复杂依赖关系的项目:

  • 效率低:每次改动一个文件就要重新输入冗长的编译命令
  • 容易出错:漏编译某个文件、忘记清理中间产物
  • 浪费资源:未修改的文件被重复编译

Makefile 的出现解决了这些问题------它定义了一套编译规则,make 命令自动按规则执行,并智能地只重新编译被修改过的文件(增量编译)。


二、快速上手:第一个 Makefile

2.1 项目结构

假设有一个最简单的 C 项目:

复制代码
project/
├── code.c        # 源文件--->假设我们写了hello world
└── Makefile      # 编译规则文件

2.2 编写 Makefile

makefile 复制代码
code:code.c
	gcc code.c -o code

这是最简单的Makefile格式

之后在终端输入make即可编译文件

2.3 使用

bash 复制代码
make
#终端会输出 gcc code.c -o code

2.4 语法要点

一条 Makefile 规则由两部分组成:

  • 编译目标:编译依赖文件
  • 所需要执行的命令(必须以tab键开头)

比如上述的code就是编译的目标文件,而code.c就是编译依赖

所执行的命令就是gcc code.c -o code

2.5 链式依赖

我们以C代码的编译四个过程为例

编译四个过程为

预处理---->.c文件变为.i

编译------->.i文件变为.s

汇编 ------>.s文件变为.o文件

链接------->.o文件变为可执行文件

Makefile示例

Makefile 复制代码
# 链接阶段:.o 生成可执行文件,依赖为main.o,而main.o在下一行为也有依赖,以此类推
main: main.o
	gcc main.o -o main
# 汇编阶段:.s 汇编成目标文件 .o
main.o: main.s
	gcc -c main.s -o main.o
# 编译阶段:预处理文件.i 编译成汇编.s
main.s: main.i
	gcc -S main.i -o main.s
main.i: main.c
	gcc -E main.c -o main.i

编译完成后即可生成main.i,main.s,main.o以及可执行文件main

2.6 清理编译产物

由于make会寻找Makefile的第一个目标,第一个目标就是我们要编译的目标,如果要增加一个清除所有编译产物的命令,比如clear,clean,就需要生成一个伪目标,当我们指定这个命令时,就不会去找第一个要执行的目标,而是我们指定的命令目标

在Makefile文件后添加

Makefile 复制代码
.PHONY:clean #表示声明一个伪目标clean
clean:	#目标clean
	rm -f code code.o code.s code.i

在终端执行make clean即可删除编译产物

三、标准化Makefile

可以看出其实Makefile只是将一些要编译的命令放在一个文件当中,当要执行的时候只是把命令缩短了。如何把一个Makefile能够更加拓展性的用在各个项目框架中呢

  1. 变量
    使用变量来代替一些文件名,即可做到拓展性
Makefile 复制代码
BIN=code					#生成的可执行文件名
SRC=$(wildcard *.c)		    #编译源文件,表示该文件下所有.c文件
COMPILE=gcc	        		#你要使用的编译器
CFLAGS=-c 					#编译选项,生成目标文件
#一般情况下会保留编译的目标二进制文件即.o文件
LFLAGS=-o					#链接形成可执行文件选项
OBJ=$(SRC:.c=.o)			#表示该文件夹下所有.c的同名.o文件

$(BIN):$(OBJ)
    @$(COMPILE) $^ $(LFLAGS) $@ #^是将所有的OBJ都进行使用
    @echo "将$^链接为$@"
  
%.o:%.c
	@$(COMPILE) $(CFLAGS) $< 	#<是一一对应,也就是将对应的.c文件意义对应为.o文件
	@echo "将$<编译为$@"

.PHONY:clean 
clean:
	$(RM) $(BIN) $(OBJ)

上述代码中,BIN,SRC等等是我们手动设置的变量,需要我们自己定义,而^<@等变量为自动变量,有其独特的含义

^:表示在目标与依赖关系中冒号右边的所有文件

<:表示冒号右边的文件与冒号左边的文件一一对应的命令关系

@如果在所有命令之前,表示关闭回显(也就是执行了make之后不用显示具体执行了哪个命令),如果用作变量,则表示冒号左边的所有文件

^<的区别在于

前者是指所有文件,比如gcc code1.o code2.o code3.o -o code 其中的code1~3.o就可以用^表示

后者在gcc -c code1.c -o code1.ogcc -c code2.c -o code2.ogcc -c code3.c -o code3.o这一组命令中code1~3.c可以用<来代替

注意,在使用变量时,需要在变量前添加$符号标识这是一个变量

我们还可以添加一些提示信息、比如将xxx.c编译为了xxx.o,将xxx1.o,xxx2.o,xxx3.o链接为了xxx.exe

如下

Makefile 复制代码
BIN=code.exe #编译执行的可执行文件
CC=gcc			#编译器
SRC=$(wildcard *.c)	#源文件
CFLAGS=-c		#编译选项
RM=rm -f
LFLAGS=-o
OBJ=$(SRC:.c=.o)



$(BIN):$(OBJ)
	@$(CC) $^ $(LFLAGS) $@
	@echo "将$^链接为$@"

%.o:%.c
	@$(CC) $(CFLAGS) $< 
	@echo "将$<编译为$@"


.PHONY:clean
clean:
	$(RM) $(BIN) $(OBJ)

四、增量式编译

这部分是关于Makefile的特点。

make 只会重新编译比目标文件更新的依赖文件,没改动的文件跳过不编译,节省时间,这就是增量编译。底层原理是每个文件都有修改时间戳,make 执行时自动对比:

规则格式:目标: 依赖

  1. 如果依赖文件时间晚于目标文件 → 依赖被修改,执行命令重新生成目标
  2. 如果依赖比目标旧 → 文件无改动,直接跳过这条规则,不执行 gcc

这一点其实在目前很多IDE上都有体现。

相关推荐
qiu_lovejun9981 小时前
linux安装docker和redis和rabbitmq和nginx和rocketmq和kafka
linux·redis·docker·kafka·rabbitmq·rocketmq
运维大师2 小时前
【Linux运维极简教程】04-进程与服务管理
linux·运维
源码学社2 小时前
Spug 3.0 Ubuntu 22.04 Docker 部署文档
linux·ubuntu·docker·windows安装·spug
崖边看雾2 小时前
记录Python学习——第一章环境安装下载(Windows)
windows·python·学习·pycharm
dankokoko2 小时前
7.13-7.14学习
学习
mounter6253 小时前
Linux 7.2 内核技术前瞻:核心 subsystems 合并与重构深度解析
linux·kernel
A_humble_scholar3 小时前
Linux(十七)深入多线程编程:同步原语与实战指南
linux·运维·c++
老马聊技术3 小时前
Rocky Linux 9.0 安装 MySQL8.x详细教程
linux·数据库
一楼的猫3 小时前
网文AI辅助怎么过审?番茄200+维度检测与去AI味实战
人工智能·学习·机器学习·自然语言处理·ai写作