make / Makefile

目录:

  • [1. make/Makefile的使用](#1. make/Makefile的使用)
  • [2. make 与 Makefile 的关系](#2. make 与 Makefile 的关系)
    • [2.1 角色分工](#2.1 角色分工)
    • [2.2 工作流程](#2.2 工作流程)
  • [3. Makefile 的核心](#3. Makefile 的核心)
  • [4. make 的工作](#4. make 的工作)
  • [5. 伪目标 .PHONY](#5. 伪目标 .PHONY)
  • [6. 变量](#6. 变量)
    • [6.1. 链接规则](#6.1. 链接规则)
    • [6.2. 模式匹配规则,编译.c 生成.o](#6.2. 模式匹配规则,编译.c 生成.o)
  • [7. 函数](#7. 函数)
  • [8. 使用示例](#8. 使用示例)

1. make/Makefile的使用

在写 C/C++ 项目时,如果每次改一个文件都要手敲:

复制代码
gcc -c main.c -o main.o
gcc -c utils.c -o utils.o
gcc main.o utils.o -o app

文件一多,命令又长又重复,还容易漏掉某个文件、忘记重新编译。更麻烦的是:改了一个 .c 文件,其实只需要重新编译它,再链接一次就行,但手敲时往往全量重编,浪费时间。

make 就是解决这个问题的工具:它是一个自动化构建工具 ,读一个叫 Makefile 的脚本,按照里面写的规则,自动判断哪些文件需要重新编译,然后执行对应命令。

2. make 与 Makefile 的关系

一句话概括:

make 是可执行程序(命令工具),Makefile 是给 make 读取的配置脚本文件。make 读 Makefile,然后根据里面描述的规则,自动执行编译、构建等工作。

2.1 角色分工

  • make :程序,Linux 自带的构建工具。 它本身不知道怎么编译代码,只会去读取规则文件,解析规则,对比文件时间戳,执行 shell 命令 。 你在终端敲的 make,就是运行这个程序。
  • Makefile :文本文件(文件名固定,首字母大写,也可以小写 makefile)。 里面写好:目标文件、依赖关系、要执行的命令。 它是给 make 看的 "说明书" ,没有 Makefile,直接敲 make 会报错:No makefile found。
名称 说明
make 可执行程序,构建工具本身
Makefile / makefile 描述构建规则的文本文件,make 读取它
规则(rule) Makefile 的基本单位,一条规则 = 目标 + 依赖 + 命令

2.2 工作流程

复制代码
终端输入 make
    ↓
make程序 寻找当前目录 Makefile / makefile
    ↓
make 解析Makefile,读取里面的规则(目标:依赖 + 命令)
    ↓
比较【目标文件】和【依赖文件】的时间戳
    - 如果依赖更新(修改过源码):执行命令,重新生成目标
    - 如果目标最新:跳过,不执行(增量构建,只编译改动文件)

执行时直接在项目目录敲

复制代码
make

make 会自动在当前目录找 Makefile 或 makefile(也可以 make -f 自定义文件名)。

3. Makefile 的核心

Makefile 最基本的结构:

复制代码
目标(target): 依赖(prerequisites)
	命令(recipe)

关键点:命令前面必须是一个 Tab,不能用空格!

一个最简单的例子:

复制代码
app: main.o utils.o
	gcc main.o utils.o -o app
main.o: main.c
gcc -c main.c -o main.o
utils.o: utils.c
gcc -c utils.c -o utils.o

含义:

  • app 依赖 main.o、utils.o,用 gcc 链接出 app

  • main.o 依赖 main.c,编译出 main.o

  • utils.o 依赖 utils.c,编译出 utils.o

4. make 的工作

make 的核心逻辑其实就两条:

  1. 依赖关系:目标依赖哪些文件?要生成目标,先保证依赖都就绪。

  2. 时间戳判断 :如果目标的修改时间比任一依赖旧,说明依赖更新了,目标过期,需要重新执行命令;否则跳过。

举例:app 依赖 main.o、utils.o。

  • 第一次 make:app 不存在 → 先递归生成 main.o、utils.o → 再链接 app。

  • 只改了 utils.c 后 make:utils.c 比 utils.o 新 → 重新编译 utils.o;main.o 没变 → 不重编;utils.o 比 app 新 → 重新链接 app。

这就是 增量构建:只做必要的重编译,效率高。

小实验:连续敲两次 make,第二次会提示 make: 'app' is up to date.,因为目标比依赖新。

改 utils.c 再 make:

屏幕只执行两条命令:

5. 伪目标 .PHONY

有些"目标"并不是真的文件,比如 clean、all、run。如果目录里恰好有个叫 clean 的文件,make 会以为目标已存在而不执行命令

所以要用 .PHONY 声

复制代码
.PHONY: clean all
all: app
clean:
rm -f *.o app

6. 变量

手写一堆 gcc、文件名容易重复,用变量维护

复制代码
# 通常放在最顶部
TARGET = app        # 最终生成的可执行程序名
CC = gcc            # 编译器
CFLAGS = -Wall -g   # 编译参数:开启警告 + 加入调试信息
SRCS = main.c foo.c # 源码文件列表
OBJS = $(SRCS:.c=.o)# 后缀替换,把 .c 全部换成 .o → main.o foo.o

示例:

复制代码
CC      = gcc
CFLAGS  = -Wall -g
TARGET  = app
OBJS    = main.o utils.o
$(TARGET): $(OBJS)
$(CC) $(OBJS) -o $(TARGET)
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
.PHONY: clean
clean:
rm -f $(OBJS) $(TARGET)

6.1. 链接规则

复制代码
$(TARGET): $(OBJS)
	$(CC) $(OBJS) -o $(TARGET)
  • $(TARGET):读取变量,代表最终可执行文件(比如app)

  • $(OBJS):所有的目标文件(main.o foo.o) 含义:可执行文件 TARGET 依赖所有 .o 文件 。 当任意.o文件更新,执行下面命令:

    gcc main.o foo.o -o app

作用:链接阶段 ,把多个.o目标文件合并,生成最终程序。

6.2.模式匹配规则,编译.c 生成.o

复制代码
%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@
  • %:Makefile 的通配符,匹配任意文件名
  • %.o: %.c:代表:任何 xxx.o 文件,依赖同名的 xxx.c
  • $(CC):编译器 gcc
  • $(CFLAGS):编译选项(警告、调试选项)
  • -c:只编译,不链接 ,生成.o目标文件
  • $<:自动变量 ,代表规则里第一个依赖文件 (也就是xxx.c)
  • $@:自动变量 ,代表当前规则的目标文件 (也就是xxx.o)

示例:当需要main.o,make 会自动匹配这条规则,执行:

复制代码
gcc -Wall -g -c main.c -o main.o

6.3 伪目标 clean,清理编译产物

复制代码
.PHONY: clean
clean:
	rm -f $(OBJS) $(TARGET)

.PHONY: clean 声明 clean 是伪目标:它不是磁盘上的文件,只是一个动作。

如果当前目录恰好存在一个名叫clean的文件,不加.PHONY时,make clean会判断:文件已经存在且最新,不会执行删除命令。加上就可以避免这个 bug。

clean: 目标 执行 make clean 触发下面 shell 命令:

复制代码
rm -f main.o foo.o app
  • rm:删除命令
  • -f:强制删除,文件不存在时不会报错 作用:删除所有.o中间文件和可执行程序,清空编译产物。

变量总结:

符号 含义
$(TARGET) 最终可执行程序(自定义变量)
$(OBJS) 所有.o 目标文件(自定义变量)
$(CC) 编译器(一般 gcc)
$(CFLAGS) 编译选项
% 通配符,匹配任意文件名
$< 第一个依赖文件
$@ 当前规则的目标文件

7. 函数

Makefile 自带一些函数,最常用的几个:

复制代码
SRCS = $(wildcard *.c)          # 找出所有 .c 文件
OBJS = $(SRCS:.c=.o)            # 把 .c 后缀替换成 .o
  • $(wildcard *.c):通配符展开

  • $(SRCS:.c=.o):后缀替换

这样新加 .c 文件不用改 Makefile:

复制代码
CC      = gcc
CFLAGS  = -Wall -g
TARGET  = app
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
$(TARGET): $(OBJS)
$(CC) $^ -o $@
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
.PHONY: clean
clean:
rm -f $(OBJS) $(TARGET)

8. 使用示例

复制代码
BIN=xin.exe
#SRC=$(shell ls *.c)
SRC=$(wildcard *.c) # wildcard函数,获取当前目录下的所有的原文件
OBJ=$(SRC:.c=.o)
$(BIN):$(OBJ)
@gcc -o $@ $^
@echo "linking $^ to $@ ... done"
%.o:%.c
@gcc -c $<
@echo "compiling $< to $@ ... done"
.PHONY:clean
clean:
.PHONY:test
test:
@echo "Debug-------"
@echo $(SRC);
@echo "Debug-------"
@echo $(OBJ);
@echo "Debug-------"
相关推荐
ZhangJun951 小时前
Linux常用命令笔记,中高级运维高频指令,并附上vim常见操作
linux·运维·centos·云计算·vim
逐流人1 小时前
Ceph对象存储与文件系统存储管理:从RADOS网关、S3与Swift到CephFS快照同步
linux·运维·ceph·云原生·云计算·swift·rados
分布式存储与RustFS1 小时前
日志目录的预算是 2 GiB:轮转、保留和压缩这三件事
运维·云原生·开源·对象存储·分布式存储·s3·性能基准
wdfk_prog2 小时前
Wi-Fi Direct 教程 05:control socket 与 eloop——P2P_FIND 怎样进入 wpa_supplicant 命令解析器
运维·服务器·后端·网络协议·ubuntu·p2p·wifi-direct
Golden_Chen2 小时前
在 Ubuntu 上搭建 FTP 服务器
linux·运维·服务器
ZMacros2 小时前
Live-build方法构建debian和ubuntu
linux·ubuntu·debian
byte轻骑兵2 小时前
【BlueZ 】netlink 在 BlueZ 中的应用:用户态与内核态的配置消息传递
linux·bluez·电脑蓝牙·嵌入式蓝牙
xbzb2 小时前
Nginx 反向代理总 404?Location 匹配与 proxy_pass 尾斜杠避坑手册
linux·运维·nginx·虚拟主机
Elastic 中国社区官方博客2 小时前
14 个 alerts,1 个 incident:使用 Elasticsearch 中的 ES|QL 衡量 alerting rule 噪声
大数据·运维·数据库·elasticsearch·搜索引擎·全文检索