Makefile 入门与实践指南


在这里插入图片描述

Makefile 是用于 make 工具的配置文件,它定义了如何编译和链接你的项目,让构建过程自动化。


一、核心概念

make 的核心思想是 “目标”(Target)“依赖”(Dependencies)

  • 目标 (Target):你想要生成的文件(比如可执行文件 myapp)或者一个伪目标(比如 clean)。
  • 依赖 (Dependencies):生成这个目标所必须的文件(比如源代码 .cpp 文件、头文件 .h 文件)。
  • 命令 (Commands):当依赖文件比目标文件更新(或目标不存在)时,需要执行的 shell 命令(比如 g++ -c main.cpp)。

基本语法:

目标: 依赖1 依赖2 ...
<TAB> 命令1
<TAB> 命令2
...

关键点:

  • 命令前面必须是 TAB,不能是空格!这是 Makefile 最常见的错误之一。
  • make 会检查依赖文件的时间戳。如果任何一个依赖文件比目标文件新,make 就会执行对应的命令来更新目标。

二、一个简单的例子

假设你有一个 C++ 项目,包含两个源文件:

  • main.cpp
  • utils.cpp
  • utils.h (被 main.cpputils.cpp 包含)

目标: 生成一个名为 myapp 的可执行文件。

Step 1: 最简单的 Makefile
# Makefile# 目标: myapp
# 依赖: main.o 和 utils.o (编译后的目标文件)
# 命令: 链接两个 .o 文件生成 myapp
myapp: main.o utils.og++ main.o utils.o -o myapp# 目标: main.o
# 依赖: main.cpp 和 utils.h (因为 main.cpp 包含了它)
# 命令: 编译 main.cpp 生成 main.o
main.o: main.cpp utils.hg++ -c main.cpp -o main.o# 目标: utils.o
# 依赖: utils.cpp 和 utils.h
# 命令: 编译 utils.cpp 生成 utils.o
utils.o: utils.cpp utils.hg++ -c utils.cpp -o utils.o# 伪目标: clean (清理编译产物)
# .PHONY 告诉 make 这不是一个真实文件,避免和同名文件冲突
.PHONY: clean
clean:rm -f myapp main.o utils.o# 伪目标: all (默认目标,通常放在最前面)
# 当你只输入 'make' 时,会执行这个目标
.PHONY: all
all: myapp

如何使用:

  1. 将以上内容保存为 Makefile(注意大小写和无后缀)。
  2. 在终端中,进入包含 Makefile 和源代码的目录。
  3. 输入 makemake 会找到 all 目标,并执行 myapp 目标,从而编译整个项目。
  4. 输入 make clean 可以删除编译生成的文件。

三、使用变量让 Makefile 更灵活

直接在命令里写编译器和选项很不方便。我们可以用变量。

# Makefile - 使用变量# 定义变量
CC = g++                 # 编译器
CFLAGS = -Wall -g        # 编译选项: -Wall 显示所有警告, -g 生成调试信息
LDFLAGS =                # 链接选项 (这里为空):链接动静态库
TARGET = myapp           # 最终可执行文件名
OBJS = main.o utils.o    # 所有目标文件# 链接目标
$(TARGET): $(OBJS)$(CC) $(LDFLAGS) $(OBJS) -o $(TARGET)# 编译目标文件 (依赖关系保持不变)
main.o: main.cpp utils.h$(CC) $(CFLAGS) -c main.cpp -o main.outils.o: utils.cpp utils.h$(CC) $(CFLAGS) -c utils.cpp -o utils.o# 清理
.PHONY: clean
clean:rm -f $(TARGET) $(OBJS)

现在,如果你想换编译器(比如用 clang++),只需要修改 CC = clang++ 这一行。


四、使用自动推导规则 (Implicit Rules)

make 内置了一些常识性的规则。例如,它知道如何从 .cpp 文件生成 .o 文件。

我们可以利用这一点,简化编译规则:

# Makefile - 使用内置规则CC = g++
CFLAGS = -Wall -g
TARGET = myapp
# 注意: 这里 OBJS 仍然需要明确列出,因为 make 不知道你的源文件是哪些
OBJS = main.o utils.o.PHONY: all
all: $(TARGET)$(TARGET): $(OBJS)$(CC) $(LDFLAGS) $(OBJS) -o $(TARGET)# 删除了 main.o 和 utils.o 的显式规则!
# make 会自动使用内置规则: $(CC) $(CFLAGS) -c source.cpp -o source.o.PHONY: clean
clean:rm -f $(TARGET) $(OBJS)

只要 CFLAGS 设置正确,make 就会自动用 g++ -Wall -g -c main.cpp -o main.o 这样的命令。


五、自动依赖生成 (推荐)

手动维护头文件依赖(如 main.o: main.cpp utils.h)非常容易出错且麻烦。我们可以让编译器帮我们生成。

# Makefile - 自动依赖生成CC = g++
CFLAGS = -Wall -g
TARGET = myapp
SRCS = main.cpp utils.cpp    # 源文件列表
OBJS = $(SRCS:.cpp=.o)       # 将 .cpp 替换为 .o, 得到 main.o utils.o
DEPS = $(SRCS:.cpp=.d)       # 依赖文件列表, 如 main.d, utils.d.PHONY: all
all: $(TARGET)$(TARGET): $(OBJS)$(CC) $(LDFLAGS) $(OBJS) -o $(TARGET)

解释:

  • %.o: %.cpp 是一个模式规则% 是通配符。它告诉 make 如何将任意 .cpp 文件编译成 .o 文件。

六、Makefile常用的几个自动变量

👍 make 提供了几个非常有用的自动变量 (Automatic Variables),它们可以在规则的命令中使用,让 Makefile 更加简洁和通用。


核心自动变量

这些变量在每个规则的命令执行时,会根据当前规则的上下文自动展开。

  1. $@ - 目标文件名 (Target)

    • 含义:代表当前规则的目标 (Target)

    • 用途:当你有多个规则,且目标名各不相同时,用 $@ 可以避免重复写目标名。

    • 例子

      # 假设当前规则是: program: main.o utils.o
      # 那么 $@ 就代表 "program"
      program: main.o utils.o$(CC) $^ -o $@  # 等价于 $(CC) main.o utils.o -o program
      
  2. $^ - 所有依赖文件名列表 (All Prerequisites)

    • 含义:代表当前规则中列出的所有依赖项 (Dependencies),用空格分隔。如果有重复的依赖,$^ 会包含重复项。

    • 用途:在链接或编译命令中,一次性引用所有依赖。

    • 例子

      # 规则: program: main.o utils.o
      # 那么 $^ 就代表 "main.o utils.o"
      program: main.o utils.o$(CC) $^ -o $@  # 等价于 $(CC) main.o utils.o -o program
      
  3. $< - 第一个依赖文件名 (First Prerequisite)

    • 含义:代表当前规则中的第一个依赖项

    • 用途:在模式规则中非常有用,比如从 .cpp 编译 .o 时,$< 就是 .cpp 文件。

    • 例子

      # 模式规则: %.o: %.cpp
      # 当 make 处理 main.cpp -> main.o 时
      # $< 代表 "main.cpp", $@ 代表 "main.o"
      %.o: %.cpp$(CC) $(CFLAGS) -c $< -o $@  # 等价于 g++ -c main.cpp -o main.o
      

为什么 $^$< 有区别?(重要!)

虽然在很多简单情况下 $^$< 看起来一样(比如只有一个依赖),但当依赖有多个或有重复时,它们就不同了。

  • $^ 包含所有依赖,包括重复项。
  • $< 只包含第一个依赖。

例子:

# 假设有一个奇怪的规则(实际很少见)
program: main.o utils.o main.o  # main.o 被列了两次@echo "Dependencies: $^"     # 输出: Dependencies: main.o utils.o main.o@echo "First dep: $<"        # 输出: First dep: main.o

在链接命令中,你通常希望传递所有目标文件,所以用 $^。而在编译单个源文件时,你只需要当前的源文件,所以用 $<


使用自动变量优化之前的 Makefile

让我们用这些自动变量来简化之前的例子:

# Makefile - 使用自动变量CC = g++
CFLAGS = -Wall -g
TARGET = myapp
SRCS = main.cpp utils.cpp
OBJS = $(SRCS:.cpp=.o)
DEPS = $(SRCS:.cpp=.d).PHONY: all
all: $(TARGET)# 链接规则: 使用 $^ (所有 .o 文件) 和 $@ (目标可执行文件)
$(TARGET): $(OBJS)$(CC) $^ -o $@.PHONY: clean
clean:rm -f $(TARGET) $(OBJS) $(DEPS)

优点:

  • 更简洁$(CC) $^ -o $@$(CC) $(LDFLAGS) $(OBJS) -o $(TARGET) 短且清晰。
  • 更通用:这个链接规则可以用于任何由 .o 文件链接而成的目标,你不需要修改命令。
  • 更可靠:在模式规则中,$< 确保只传递当前正在编译的源文件。

总结
  • $@ = 目标 (Target) - 你要生成的东西。
  • $^ = 所有依赖 (All Prerequisites) - 生成目标需要的所有输入文件(链接时用)。
  • $< = 第一个依赖 (First Prerequisite) - 通常用于编译单个源文件时的源文件名。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/91760.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/91760.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

分布式微服务--Nacos作为配置中心(补)关于bosststrap.yml与@RefreshScope

一、关于bosststrap.yml✅ bootstrap.yml 和 application.yml 的区别对比项bootstrap.ymlapplication.yml加载时机优先于 application.yml 加载&#xff08;启动早期&#xff09;程序初始化完成后加载主要用途设置应用的外部配置源、注册中心信息等设置应用内部配置&#xff0c…

[Qt]QString 与Sqlite3 字符串互动[汉字不乱码]

环境&#xff1a;Qt C&#xff08;msvc c&#xff09;1.将与数据库交互的代码文件编码转换为utf-8-bom编码&#xff0c;&#xff08;可使用notepad 进行转换&#xff09;2.在代码文件头文件中加上下面代码。//vs2010 版本是 1600 #if defined(_MSC_VER) && (_MSC_VER &…

SpringBoot启动项目详解

SpringBoot 的启动过程是一个整合 Spring 核心容器、自动配置、嵌入式服务器等功能的复杂流程&#xff0c;核心目标是 “简化配置、快速启动”。下面从入口类开始&#xff0c;逐步拆解其详细启动步骤&#xff1a;一、启动入口&#xff1a;SpringBootApplication与main方法Sprin…

PCB 控深槽如何破解 5G 基站 120℃高热魔咒?

5G 基站在高频通信下的功耗较 4G 基站提升 3-4 倍&#xff0c;射频模块、电源单元等核心部件的工作温度常突破 120℃&#xff0c;远超设备安全阈值&#xff08;≤85℃&#xff09;&#xff0c;形成制约通信稳定性的 “高热魔咒”。印制线路板&#xff08;PCB&#xff09;作为热…

NEXT.js 打包部署到服务器

在网上查了一下&#xff0c;记录一下1.首先执行打包命令&#xff0c;我这个项目是用的pnpm&#xff0c;可以根据项目需求使用 npm 或者别的pnpm run build2.打包完成后会有一个 .next 的文件夹&#xff0c;需要把下图的这些文件放到服务器。服务器需要有node环境之后就需要执行…

【AI分析】uv库自动安装脚本uv-installer-0.8.3.ps1分析

目录uv 安装脚本完整分析报告1. 脚本概述2. 参数解析3. 环境变量控制4. 核心函数详解a. Install-Binary&#xff08;主控函数&#xff09;b. Get-TargetTriple&#xff08;架构检测&#xff09;c. Download&#xff08;下载处理&#xff09;d. Invoke-Installer&#xff08;安装…

etcd 的安装与使用

介绍 Etcd 是一个 golang 编写的分布式、高可用的一致性键值存储系统&#xff0c;用于配置共享和服 务发现等。它使用 Raft 一致性算法来保持集群数据的一致性&#xff0c;且客户端通过长连接 watch 功能&#xff0c;能够及时收到数据变化通知&#xff0c;相较于 Zookeeper 框…

conda : 无法将“conda”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正

详细问题 PS C:\Users\wh109> conda init powershell conda : 无法将“conda”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写&#xff0c;如果包括路径&#xff0c;请确保路径正 确&#xff0c;然后再试一次。 所在位置 行:1 字符: 1conda init pow…

HQChart实战教程58:K线主图仿TradingView实现

本文将详细介绍如何使用HQChart实现类似TradingView风格的K线主图,包含完整的代码实现和详细注释,适合金融图表开发者和量化交易爱好者阅读。 一、TradingView风格特点分析 在开始实现前,我们先分析TradingView的K线主图核心特点: 简洁现代的UI设计:深色背景、清晰的网格…

GitPython08-源码解读

GitPython08-源码解读 1-核心知识 1&#xff09;gitPython核心代码很多都是对git命令返回的结果进行解析&#xff0c;在此补充git命令的返回内容2&#xff09;git ls-tree -> 查看某个提交或分支所对应的目录树3&#xff09;源码中Tree对应的业务逻辑 -> 获取git ls-tre…

中科院开源HYPIR图像复原大模型:1.7秒,老照片变8K画质

目录 前言 一、告别“龟速”艺术家&#xff0c;拥抱“闪电”打印机 二、不止是高清&#xff1a;它看得懂文字&#xff0c;更能理解你的心意 2.1 首先&#xff0c;它是位“文字保卫者” 2.2 其次&#xff0c;它还是个“细节创造家” 2.3 最后&#xff0c;它是一个能“听懂…

设计Mock华为昇腾GPU的MindSpore和CANN的库的流程与实现

在没有华为昇腾GPU可用的情况下用C库写一个Mock MindSpore和CANN的库&#xff0c;调用多核CPU和内存的资源来模拟GPU的算力&#xff0c;调试MindSpore和CANN的C代码&#xff0c;做这个库的基本流程步骤和详细设计是什么&#xff1f; 要创建一个Mock库来模拟华为昇腾GPU&#xf…

【AI问答记录】grafana接收query请求中未携带step参数,后端基于intervalMs和maxDataPoints等参数计算step的逻辑

让我详细分析Grafana源码中计算step的完整逻辑&#xff0c;它确实比简单的intervalMs/1000复杂得多。 完整的Step计算流程 1. 入口点&#xff1a;[models.Parse](file://F:\JavaProject\grafana-release-11.2.0\pkg\promlib\models\query.go#L190-L274)函数 在pkg/promlib/mode…

再谈亚马逊云科技(AWS)上海AI研究院7月22日关闭事件

【科技明说 &#xff5c; 科技热点关注】亚马逊云科技&#xff08;AWS&#xff09;上海AI研究院已于2025年7月22日正式解散&#xff0c;这是亚马逊在全球范围内的最后一个海外研究中心的关闭。这个消息是否是真的&#xff0c;目前得到的印证来自其研发中心的首席科学家王敏捷在…

Python中的决策树机器学习模型简要介绍和代码示例(基于sklearn)

一、决策树定义 决策树是一种监督学习算法&#xff0c;可用于**分类&#xff08;Classification&#xff09;和回归&#xff08;Regression&#xff09;**任务。 它的结构类似树状结构&#xff1a; 内部节点&#xff1a;特征条件&#xff08;如X > 2&#xff09;叶子节点&am…

Redis集群分布式(Redis Cluster)底层实现原理详细介绍

文章目录一、Redis集群概念二、集群节点1. 节点如何启动2. 节点的集群数据结构2.1 clusterNode结构2.2 clusterLink结构2.3 clusterState结构3. 节点如何加入集群三、数据分片机制1. 记录节点的槽指派信息2. 传播节点的槽指派信息3. 记录集群所有槽的指派信息4. 节点的槽指派命…

【走遍美国精讲笔记】第 1 课:林登大街 46 号

ACT 1-1 “我可以给您和您的小男孩拍张照吗&#xff1f;” 【故事梗概】 自由摄影艺术家 Richard Stewart&#xff0c;正在为编出自己的影集《走遍美国》到处拍照。今天他在由纽约市曼哈顿区到斯塔滕岛的渡船上工 作&#xff0c;回程中遇到了来自加州的一位黑人妇女 Martha Van…

Java中Lambda 表达式的解释

从 Java 8 开始&#xff0c;Lambda 表达式成为 Java 的一等公民。它不仅让代码更简洁&#xff0c;还为函数式编程打开了大门。如果你还没真正理解或使用过 Lambda&#xff0c;这篇文章就是为你写的。一、什么是 Lambda 表达式&#xff1f;Lambda 表达式是 Java 中的一种匿名函数…

Spring AI调用Embedding模型返回HTTP 400:Invalid HTTP request received分析处理

调用Embedding模型失败 Spring AI项目使用的Embedding模型是公司平台部署的&#xff0c;请求模型服务的时候报错&#xff0c;返回了HTTP 400 - Invalid HTTP request received错误。然后换成云厂商在线Embedding模型地址&#xff0c;正常调通。我用Apifox直接调用公司的模型服务…

Pytorch-02数据集和数据加载器的基本原理和基本操作

1. 为什么要有数据集类和数据加载器类&#xff1f; 一万个人会有一万种获取并处理原始数据样本的代码&#xff0c;这会导致对数据的操作代码标准不一&#xff0c;并且很难复用。为了解决这个问题&#xff0c;Pytorch提供了两种最基本的数据相关类&#xff1a; torch.utils.data…