Linux 入门五:Makefile—— 从手动编译到工程自动化的蜕变

一、概述:Makefile—— 工程编译的 “智能指挥官”

1. 为什么需要 Makefile?

  • 手动编译的痛点:当工程包含数十个源文件时,每次修改都需重复输入冗长的编译命令(如gcc file1.c file2.c -o app),且无法自动识别哪些文件需要重新编译。
  • Makefile 的核心价值:通过定义 “目标 - 依赖 - 命令” 规则,实现自动化编译。只需执行make命令,即可根据文件修改时间智能判断编译顺序,避免重复工作,大幅提升开发效率。
  • 本质:一个名为Makefile(或makefile)的文本文件,存储编译规则,由make命令解析执行。

2. 核心概念快速入门

  • 目标(Target):要生成的文件(如可执行文件app)或伪操作(如清理编译产物的clean)。
  • 依赖(Prerequisites):生成目标所需的文件(如app依赖main.ofunc.o)。
  • 命令(Command):生成目标的具体操作,需以Tab 键开头(Makefile 严格要求)。

二、简单使用:从第一个 Makefile 起步

1. 创建与编辑 Makefile

# 创建文件
touch Makefile
# 用vim编辑(推荐用Visual Studio Code等IDE提升体验)
vim Makefile

2. 编写第一个编译规则:编译单文件程序

# 目标:生成可执行文件hello,依赖hello.c
hello: hello.c# 命令:用gcc编译,-o指定输出文件名,@禁止回显命令本身@echo "正在编译hello..."gcc hello.c -o hello# 伪目标:清理编译产物,.PHONY避免与同名文件冲突
.PHONY: clean
clean:@echo "清理编译产物..."rm -f hello  # -f强制删除,即使文件不存在也不报错

3. 执行 Makefile

# 编译目标(首次执行会生成hello)
make
# 输出:
# 正在编译hello...
# (若命令前无@,会额外回显"gcc hello.c -o hello")# 清理产物
make clean
# 输出:清理编译产物...(同时删除hello文件)

4. 关键语法解析

  • 注释#开头的行,用于解释规则(如# 伪目标:清理编译产物)。
  • 自动推导:Makefile 默认知道.c文件可编译为.o文件(如main.o依赖main.c,无需显式书写规则)。
  • 伪目标:用.PHONY声明(如clean),确保即使存在同名文件,make clean也会执行命令。

三、变量:让 Makefile 告别 “硬编码”

1. 自定义变量:四种赋值方式对比

赋值符号特性示例适用场景
=递归展开(可引用后续定义的变量)CFLAGS = -Wall\nOBJECTS = $(SRCS:.c=.o)需要动态计算值的场景
:=立即展开(定义时直接计算)SRCS := $(wildcard *.c)避免递归引用导致的循环定义
+=追加值(在原有值后添加新内容)LIBS += -lm(追加数学库)逐步构建复杂参数
?=惰性赋值(仅在未定义时生效)CC ?= gcc(默认用 gcc,可被命令行覆盖)设置默认值

2. 自动变量:依赖文件的 “快捷引用”

在模式规则(如%.o: %.c)中,自动变量可简化代码:

变量含义示例(目标main.o依赖main.c
$@当前目标文件名命令中$@代表main.o
$<第一个依赖文件命令中$<代表main.c
$^所有依赖文件(去重)依赖a.c b.c时,$^代表a.c b.c
$?比目标新的依赖文件仅重新编译修改过的文件

示例:多文件编译(使用自动变量)

CC := gcc          # 立即赋值,指定编译器
CFLAGS := -Wall -g  # 编译选项:开启警告和调试信息
TARGET := app       # 目标文件名
SRCS := main.c func.c  # 显式列出源文件(或用wildcard函数自动收集)
OBJS := $(SRCS:.c=.o)  # 将.c替换为.o,生成目标文件列表$(TARGET): $(OBJS)$(CC) $(OBJS) -o $(TARGET)  # 链接所有.o文件%.o: %.c$(CC) $(CFLAGS) -c $< -o $@  # 编译单个.c到.o,$<是源文件,$@是目标文件.PHONY: clean
clean:@rm -f $(OBJS) $(TARGET)

3. 预定义变量:Makefile 的 “内置工具”

Makefile 自带常用工具变量,可直接使用:

  • CC:C 编译器(默认cc,通常设为gcc)。
  • AR:归档工具(用于静态库,默认ar)。
  • RM:删除命令(默认rm -f,自动添加-f强制删除)。
  • CXX:C++ 编译器(默认g++)。

示例:使用预定义变量

main.o: main.c$(CC) -c main.c -o main.o  # 等价于`gcc -c main.c -o main.o`(若CC=gcc)

四、函数:让 Makefile 更 “聪明”

1. 文件搜索函数:wildcard

  • 功能:按模式匹配文件,返回匹配的文件列表(支持通配符*)。
  • 语法$(wildcard 模式),如$(wildcard src/*.c)获取src/目录下所有.c文件。
  • 示例:自动收集所有源文件
    SRCS := $(wildcard *.c)  # 收集当前目录所有.c文件
    OBJS := $(SRCS:.c=.o)     # 转换为.o文件列表app: $(OBJS)$(CC) $(OBJS) -o app
    

2. 字符串替换函数:patsubst

  • 功能:按指定模式替换字符串中的部分内容。
  • 语法$(patsubst 原模式, 新模式, 字符串),如$(patsubst %.c, %.o, a.c b.cpp)a.o b.o(需配合手动处理.cpp 文件)。
  • 示例:灵活处理混合格式源文件
    SRCS := a.c b.cpp c.c
    # 分别将.c和.cpp转换为.o(需分步处理)
    C_OBJS := $(patsubst %.c, %.o, $(filter %.c, $(SRCS)))
    CPP_OBJS := $(patsubst %.cpp, %.o, $(filter %.cpp, $(SRCS)))
    OBJS := $(C_OBJS) $(CPP_OBJS)
    

五、选项:扩展 make 命令的能力

1. -f:指定非默认Makefile

  • 场景:项目存在多个 Makefile(如Makefile.linuxMakefile.win),需显式指定。
  • 用法
    make -f Makefile.linux  # 执行指定文件中的规则,而非默认的Makefile
    

2. -C:切换目录执行

  • 场景:工程分模块存放(如src/lib/目录各有独立 Makefile)。
  • 用法
    # 总控Makefile,编译所有模块
    all:@make -C src  # 进入src目录,执行该目录下的Makefile@make -C lib  # 进入lib目录,执行该目录下的Makefile.PHONY: clean
    clean:@make -C src clean  # 清理src模块@make -C lib clean  # 清理lib模块
    

3. 其他实用选项

选项含义示例
-n干运行,仅打印命令不执行(调试用)make -n 查看编译步骤是否正确
-s静默模式,不回显命令(仅显示输出)make -s 隐藏编译命令,输出更简洁
-j N并行编译,N 为线程数(加快多核 CPU 编译速度)make -j 4 使用 4 个线程编译

六、实战模板:三种常用 Makefile 写法

模板一:生成可执行文件(多文件编译)

# 一、变量定义
CC := gcc              # C编译器
CFLAGS := -Wall -g -Iinclude  # 编译选项:警告+调试+头文件路径
SRCS := $(wildcard src/*.c)  # 自动收集src目录下所有.c文件
OBJS := $(patsubst src/%.c, obj/%.o, $(SRCS))  # 生成obj/目录下的.o文件# 二、目标规则
# 1. 最终目标:生成可执行文件app
app: $(OBJS)@echo "链接生成可执行文件..."$(CC) $(OBJS) -o app -Llib -lm  # -L指定库路径,-lm链接数学库# 2. 模式规则:src/xxx.c → obj/xxx.o(自动创建obj目录)
obj/%.o: src/%.c@mkdir -p obj  # 确保obj目录存在$(CC) $(CFLAGS) -c $< -o $@# 三、伪目标
.PHONY: clean
clean:@echo "清理编译产物..."@rm -f app $(OBJS)  # 删除可执行文件和所有.o文件@rm -rf obj  # 删除obj目录

模板二:生成动态库(.so 文件)

# 一、变量定义
SO_NAME := libmylib.so  # 动态库名称
CC := gcc
CFLAGS := -fPIC -Wall  # -fPIC生成位置无关代码(动态库必需)
SHLIB_FLAGS := -shared  # 生成动态库的关键选项
SRCS := $(wildcard src/*.c)
OBJS := $(SRCS:.c=.o)# 二、目标规则
# 1. 生成动态库
$(SO_NAME): $(OBJS)$(CC) $(SHLIB_FLAGS) $(OBJS) -o $(SO_NAME)# 2. 编译.o文件(与可执行文件规则类似)
%.o: %.c$(CC) $(CFLAGS) -c $< -o $@# 三、伪目标
.PHONY: clean
clean:@rm -f $(OBJS) $(SO_NAME)

模板三:生成静态库(.a 文件)

# 一、变量定义
A_NAME := libmylib.a  # 静态库名称
AR := ar rcs          # ar命令参数:r(添加)c(创建)s(生成索引)
CC := gcc
CFLAGS := -Wall
SRCS := $(wildcard src/*.c)
OBJS := $(SRCS:.c=.o)# 二、目标规则
# 1. 生成静态库(打包所有.o文件)
$(A_NAME): $(OBJS)$(AR) $(A_NAME) $(OBJS)  # 将.o文件打包成静态库# 2. 编译.o文件
%.o: %.c$(CC) $(CFLAGS) -c $< -o $@# 三、伪目标
.PHONY: clean
clean:@rm -f $(OBJS) $(A_NAME)

七、常见易错点与避坑指南

  1. 命令前必须用 Tab 键

    • 错误:命令行以空格开头,导致 “missing separator (did you mean TAB instead of 8 spaces?)” 错误。
    • 正确:所有命令行必须以 Tab 键开头(可在编辑器中设置 Tab 为 4 个空格,但最终需确保是 Tab 符)。
  2. 伪目标未声明.PHONY

    • 后果:若当前目录存在名为clean的文件,make clean会认为目标已存在,不执行清理命令。
    • 正确:始终为清理等伪目标添加.PHONY: clean声明。
  3. 变量引用格式错误

    • 错误:直接写变量名(如CC=gcc),应使用$(CC)引用变量。
    • 正确:所有变量引用需用$(变量名)${变量名}格式。
  4. 依赖关系遗漏

    • 后果:头文件(.h)修改后,未将其加入依赖,导致.o文件未重新编译。
    • 正确:在规则中显式依赖头文件(如main.o: main.c defs.h),或利用 Makefile 自动推导(需确保头文件包含正确)。

八、作业:从模仿到独立编写

1. 任务一:解析经典 Makefile

  • 下载开源项目(如nginxredis)的 Makefile,分析以下内容:
    ① 如何定义编译选项(CFLAGSCXXFLAGS)?
    ② 静态库 / 动态库的生成规则有何不同?
    ③ clean目标如何处理多层目录的编译产物?

2. 任务二:编写三个万能模板(强化版)

  • 可执行文件模板:添加对 C++ 文件的支持(.cpp文件用g++编译),使用wildcard递归搜索子目录源文件(如src/**/*.c)。
  • 动态库模板:添加版本号(如libmylib.so.1.0.0),使用ln -s创建软链接(如libmylib.so → libmylib.so.1.0.0)。
  • 静态库模板:支持多架构编译(如同时生成x86arm版本),通过变量ARCH切换编译选项。

3. 任务三:实战复杂工程

创建一个包含以下结构的项目:

project/
├─ Makefile       # 总控Makefile
├─ src/
│  ├─ main.c
│  ├─ func.c
│  └─ Makefile     # 模块Makefile
├─ include/
│  └─ func.h
└─ lib/           # 编译生成的库文件存放目录

要求总控 Makefile 使用-C选项调用src/目录下的 Makefile,最终在lib/目录生成可执行文件。

总结:Makefile 让工程编译 “化繁为简”

通过掌握 Makefile 的核心规则、变量、函数和选项,你将实现从手动编译到自动化编译的跨越。记住以下关键点:

  • 规则是基础:每个目标必须明确依赖和命令,利用自动推导简化常规编译步骤。
  • 变量提效率:自定义变量减少重复输入,自动变量和预定义变量提升代码简洁性。
  • 函数增智能wildcardpatsubst自动处理文件列表,适应复杂工程结构。
  • 选项扩场景-f-C应对多 Makefile 和分模块编译,-j加速编译过程。

现在,打开你的项目,用 Makefile 替代繁琐的手动命令,让编译过程从此高效、智能!

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

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

相关文章

Python-Django+vue二手电子设备交易平台功能说明

❥(^_-) 上千个精美定制模板,各类成品Java、Python、PHP、Android毕设项目,欢迎咨询。 ❥(^_-) 程序开发、技术解答、代码讲解、文档,💖文末获取源码+数据库+文档💖 💖软件下载 | 实战案例 💖文章底部二维码,可以联系获取软件下载链接,及项目演示视频。 本项目…

数据库管理工具实战:IDEA 与 DBeaver 连接 TDengine(二)

五、DBeaver 连接 TDengine 实战 5.1 安装 DBeaver 下载安装包&#xff1a;访问 DBeaver 官方网站&#xff08;https://dbeaver.io/download/ &#xff09;&#xff0c;根据你的操作系统选择合适的安装包。如果是 Windows 系统&#xff0c;下载.exe 格式的安装文件&#xff1…

Spring Boot接口返回Long类型的数据时丢失精度的全局处理

1、问题 当实体类中的字段为Long类型时&#xff0c;通过Ajax请求返回给前段&#xff0c;在js中数据会丢失精度 直接通过postman请求或通过浏览器请求&#xff0c;看下响应则不会丢失精度 2、处理方式 1、使用JsonSerialize注解 JsonSerialize(using ToStringSerializer.…

英伟达Llama-3.1-Nemotron-Ultra-253B-v1语言模型论文快读:FFN Fusion

FFN Fusion: Rethinking Sequential Computation in Large Language Models 代表模型&#xff1a;Llama-3.1-Nemotron-Ultra-253B-v1 1. 摘要 本文介绍了一种名为 FFN Fusion 的架构优化技术&#xff0c;旨在通过识别和利用自然并行化机会来减少大型语言模型&#xff08;LLM…

Django学习记录-1

Django学习记录-1 虽然网上教程都很多&#xff0c;但是感觉自己记录一下才属于自己&#xff0c;之后想找也方面一点&#xff0c;文采不佳看的不爽可绕道。 参考贴 从零开始的Django框架入门到实战教程(内含实战实例) - 01 创建项目与app、加入静态文件、模板语法介绍&#xff…

Python爬虫第7节-requests库的高级用法

目录 前言 一、文件上传 二、Cookies 三、会话维持 四、SSL证书验证 五、代理设置 六、超时设置 七、身份认证 八、Prepared Request 前言 上一节&#xff0c;我们认识了requests库的基本用法&#xff0c;像发起GET、POST请求&#xff0c;以及了解Response对象是什么。…

Python 要致富先修路

今天准备在原有基础上重新深入学习并记录python学习进程。 # 整体思路 不废话&#xff1a; 阶段1&#xff1a;精选入门电子教程坚持学习&#xff1b; 阶段2&#xff1a;跟着教程学习代码思维&#xff0c;做好学习笔记并构建知识库方便以后速查&#xff1b; 阶段3&#xff…

微服务无感发布实践:基于Nacos的客户端缓存与故障转移机制

微服务无感发布实践&#xff1a;基于Nacos的客户端缓存与故障转移机制 背景与问题场景 在微服务架构中&#xff0c;服务的动态扩缩容、滚动升级是常态&#xff0c;而服务实例的上下线需通过注册中心&#xff08;如Nacos&#xff09;实现服务发现的实时同步。但在实际生产环境…

2025年的Android NDK 快速开发入门

十年前写过一篇介绍NDK开发的文章《Android实战技巧之二十三&#xff1a;Android Studio的NDK开发》&#xff0c;今天看来已经发生了很多变化&#xff0c;NDK开发变得更加容易了。下面就写一篇当下NDK开发快速入门。 **原生开发套件 (NDK) **是一套工具&#xff0c;使开发者能…

Shell 编程之条件语句

目录 条件测试操作 文件测试 整数值比较 字符串比较 逻辑测试 if 条件语句 if语句的结构 1、单分支 if 语句 2、双分支 if 语句 3、多分支 if 语句 if语句应用实例 1、单分支 if 语句应用 2、双分支 if 语句应用 3、多分支 if 语句应用 case 分支语句 case语句的结构 case语…

【模板】缩点

洛谷p3387 思路: 算法:tarjan算法 根据题意,我们只要找到一个路径,使得最终权重最大即可,首先,根据题目可知,如果一个点在一个环上,那么我们就将这整个环都选上,题目上允许我们能够重复走,因此,我们可以将环缩成点,将环所称点后,就可以转换成树,从没有父节点的结点开始,我们向…

js触发隐式类型转换的场景

JavaScript 的隐式类型转换&#xff08;Implicit Type Coercion&#xff09;会在某些操作或上下文中自动触发&#xff0c;将值从一种类型转换为另一种类型。以下是常见的触发场景&#xff1a; 1. 使用 &#xff08;宽松相等&#xff09;比较时 会尝试将两边的值转换为相同类型后…

c++将jpg转换为灰度图

c将jpg转换为灰度图 step1:添加依赖 下载这两个文件&#xff0c;放在cpp同一目录下&#xff0c;编译生成 https://github.com/nothings/stb/blob/master/stb_image_write.h https://github.com/nothings/stb/blob/master/stb_image.hstep2:C:\Users\wangrusheng\source\repos…

python——正则表达式

一、简介 在 Python 中&#xff0c;正则表达式主要通过 re 模块实现&#xff0c;用于字符串的匹配、查找、替换等操作。 二、Python的re模块 使用前需要导入&#xff1a; import re 三、常用方法 方法描述re.match(pattern, string)从字符串开头匹配&#xff0c;返回第一个匹…

Soybean Admin 配置vite兼容低版本浏览器、安卓电视浏览器(飞视浏览器)

环境 window10 pnpm 8.15.4 node 8.15.4 vite 5.1.4 soybean admin: 1.0.0 native-ui: 2.38.0 小米电视 MIUI TV版本&#xff1a;MiTV OS 2.7.1886(稳定版) 飞视浏览器&#xff1a;https://www.fenxm.com/1220.html在小米电视安装飞视浏览器可以去小红书查安装教程&#xff1a…

系统与网络安全------网络通信原理(1)

资料整理于网络资料、书本资料、AI&#xff0c;仅供个人学习参考。 文章目录 网络通信模型协议分层计算机网络发展计算机网络功能什么是协议为什么分层邮局实例 OSI模型OSI协议模型OSI七层模型OSI七层的功能简介 TCP/IP模型OSI模型与TCP/IP模型TCP/IP协议族的组成各层PDU设备与…

如何使用通义灵码完成PHP单元测试 - AI辅助开发教程

一、引言 在软件开发过程中&#xff0c;测试是至关重要的一环。然而&#xff0c;在传统开发中&#xff0c;测试常常被忽略或草草处理&#xff0c;很多时候并非开发人员故意为之&#xff0c;而是缺乏相应的测试思路和方法&#xff0c;不知道如何设计测试用例。随着 AI 技术的飞…

批量清空图片的相机参数、地理位置等敏感元数据

我们在使用相机或者手机拍摄照片的时候&#xff0c;照片中都会带有一些敏感元数据信息&#xff0c;比如说相机的型号&#xff0c;参数&#xff0c;拍摄的时间地点等等。这些信息虽说不是那么引人注意&#xff0c;但是在某些时候他是非常隐私非常重要的。如果我们将这些信息泄露…

SQL优化算法解析 | PawSQL 如何将EXISTS子查询“秒拆“为JOIN连接

在数据库性能调优中,子查询优化是提升查询效率的关键点之一。今天,我们将分享一个使用 PawSQL 对EXISTS子查询进行重写优化的案例,展示如何通过合理的SQL重写与索引设计,实现超过487516.45%的性能提升! 一、案例分析&#xff1a;EXISTS子查询的性能困境 这个查询的目的是找出…

大模型day1 - 什么是GPT

什么是GPT 全称 Generative Pre-trained Transformer 是一种基于 Transformer 架构的大规模 预训练 语言模型&#xff0c;由OpenAI研发&#xff0c;但GPT仅仅只是借鉴了Transformer 中 Decoder 的部分&#xff0c;并且做了升级 Transformer 架构 Transformer架构 是一种用于…