【Makefile 专家之路 | 基础篇】02. 初试锋芒:编写第一个 Makefile 与运行机制深度剖析

文章目录

  • [一、 实战场景:一个简单的 C 工程](#一、 实战场景:一个简单的 C 工程)
  • [二、 编写你的第一个 Makefile](#二、 编写你的第一个 Makefile)
  • [三、 深度剖析:make 的递归工作逻辑](#三、 深度剖析:make 的递归工作逻辑)
  • [四、 重点:Makefile 的变量初步](#四、 重点:Makefile 的变量初步)

一、 实战场景:一个简单的 C 工程

假设你现在正在开发一个 Android 原生模块(Native Module),你有三个文件:

  1. main.c:主程序,调用了 utils.c 里的函数。
  2. utils.c:工具函数实现。
  3. utils.h:工具函数声明。

如果没有 Makefile,你的编译命令是:

bash 复制代码
gcc -c main.c           # 生成 main.o
gcc -c utils.c          # 生成 utils.o
gcc -o my_app main.o utils.o  # 链接生成可执行文件

二、 编写你的第一个 Makefile

现在,我们在同级目录下创建一个名为 Makefile 的文本文件,内容如下:

bash 复制代码
# 最终目标:可执行文件 my_app
my_app: main.o utils.o
	gcc -o my_app main.o utils.o

# 中间目标:main.o 依赖 main.c 和 utils.h
main.o: main.c utils.h
	gcc -c main.c

# 中间目标:utils.o 依赖 utils.c
utils.o: utils.c utils.h
	gcc -c utils.c

# 伪目标:清理编译产物
clean:
	rm -f my_app main.o utils.o

三、 深度剖析:make 的递归工作逻辑

当你输入 make 并回车时,后台发生了精彩的"递归搜索":

  1. 确定终极目标:make 默认会去找文件里的第一个目标,即 my_app。
  2. 追溯依赖链 :
    • 要生成 my_app,发现需要 main.o 和 utils.o。
    • 于是 make 暂时放下 my_app,跳去寻找生成 main.o 的规则。
    • 找到 main.o 规则后,检查 main.c 是否存在。如果存在,对比时间戳,决定是否执行 gcc -c main.c。
    • 同理,处理 utils.o。
  3. 最终合成: 只有当所有的 .o 文件都准备好(或更新完)之后,make 才会回到第一条规则,执行最后的链接命令生成 my_app。

这就是"自顶向下"的任务分解,和"自底向上"的编译执行。

四、 重点:Makefile 的变量初步

在 Android 源码中,你很少看到直接写死的文件列表。为了方便维护,我们会引入变量。

  • 改写版:
bash 复制代码
OBJS = main.o utils.o  # 定义变量

my_app: $(OBJS)        # 使用变量
	gcc -o my_app $(OBJS)

这样如果你以后多了一个 process.c,只需要修改 OBJS 变量即可,不用到处改规则。

五、 💡 安卓工程师的记忆卡片

  1. .h 文件的依赖: 注意看 main.o: main.c utils.h。为什么要把 .h 写在依赖里?
    • 原因: 因为如果修改了 utils.h(比如改了一个结构体定义),虽然 .c 没变,但编译出的 .o 逻辑必须更新。
    • 安卓实战: Android 源码非常庞大,手动维护 .h 依赖极度痛苦。在后面的【实战篇 13】中,我会教你 Android 是如何通过 GCC 参数自动生成这些头文件依赖的。
  2. clean 的重要性: clean 规则没有依赖,只有命令。它能帮你快速"重置"环境。
    • 安卓实战: 对应 Android 里的 make clean 或删除 out/ 目录的操作。

【本篇自测】

  1. 如果我在 Makefile 中把 clean 规则放在第一行,直接输入 make 会发生什么?
  2. 如果我运行了 make 生成了 my_app,接着不做任何修改,再次输入 make,它会执行命令吗?为什么?
相关推荐
Linux运维技术栈3 小时前
CVE-2026-91403 高危漏洞修复:Rocky Linux 9 多版本 JDK 环境清理实操
java·linux·运维
slongzhang_4 小时前
linux 递归删除源码文件|递归删除目录下 *.js,但排除 *.min.js
linux·开发语言·javascript
叼烟扛炮4 小时前
Linux 学习笔记(三十三):线程互斥——互斥锁(Mutex)与死锁
linux·运维·服务器·笔记·学习·1024程序员节
无足鸟ICT5 小时前
【RHCA+】who命令(查看已登陆用户)
linux·运维·服务器
Escalating_xu5 小时前
【Linux 网络编程】应用层协议 HTTP(下):Header、连接管理与 C++ HTTP 服务器
linux·服务器·网络
GeW7 小时前
新质生产力路线图落地,Linux运维与数据库架构师的机会清单
linux
Brilliantwxx7 小时前
【Linux】 文件访问与路径解析:目录、文件名、dentry、路径缓冲与 dcache 全解析
linux·运维·服务器·开发语言·spring
father_12278 小时前
Greenplum 集群故障恢复与主备同步机制实战
linux·网络·greenplum
ynchyong8 小时前
linux中的 sed` 与 `seq`
linux·运维·sed·seq·文本工具
七牛云行业应用9 小时前
DeepSeek Harness(dsh)安装完整指南:Windows、macOS、Linux 三平台 + 桌面端与 Web 端全覆盖
linux·windows·macos·ai·ai编程