第9课:Linux开发工具(四):make与makefile

news2026/5/15 21:47:57
第9课Linux开发工具四make与makefile一、为什么我们需要 Makefile1.1 IDE 背后的秘密在使用 Visual Studio 等 IDE 时我们只需按下 F5 或点击编译按钮程序就会自动完成编译、链接并运行。但我们从未关心过这个过程是如何发生的。IDE 实际做的事情将每个源文件.c/.cpp分别编译成目标文件Windows 下是 .objLinux 下是 .o将所有目标文件和库文件链接在一起形成最终的可执行程序.exe核心结论当项目只有1-2个源文件时手动编译没问题。但当项目有100个、1000个甚至上万个源文件时手动输入编译命令就变得不现实了。1.2 什么是构建老师不喜欢抽象名词用大白话解释构建 把你的所有源文件走一遍程序翻译的完整流程最终形成可执行程序的整个过程。1.3 Windows vs Linux 构建方式对比系统自动化构建工具特点WindowsVisual Studio 集成环境完全自动化用户无感知Linuxmake Makefile工具独立需要开发者自己维护构建规则核心结论make 是一条命令Makefile 是一个文件。两者配合完成 Linux 下的项目自动化构建。二、Makefile 的核心灵魂依赖关系与依赖方法2.1 第一个最简单的 Makefile假设我们有一个test.c源文件要生成test.exe可执行程序。步骤在当前目录下新建一个文件文件名必须是Makefile或makefile推荐首字母大写在文件中写入以下内容test.exe: test.c gcc -o test.exe test.c在命令行中执行make命令即可自动编译生成test.exe2.2 核心概念详解Makefile 的每一条规则都由两部分组成依赖关系目标文件: 依赖文件列表冒号左侧要生成的目标文件冒号右侧生成目标文件所需要的所有依赖文件依赖方法以Tab 键开头的命令行说明如何从依赖文件生成目标文件易错警告依赖方法必须以 Tab 键开头不能用空格代替这是 Makefile 的语法强制要求没有为什么。2.3 生活化理解为什么需要两者同时存在老师用了一个非常形象的例子月底你给爸爸打电话“爸我是你儿子。”只表明了依赖关系你爸会很奇怪“这小子是不是疯了”正确的做法是“爸我是你儿子今天中午12点前给我农行卡打1000块钱。”同时表明了依赖关系和依赖方法结论任何事情的完成都必须同时具备合理的依赖关系和可行的依赖方法。Makefile 也不例外。三、Makefile 的工作原理推导栈与递归思想3.1 完整的程序翻译过程为了理解 Makefile 的推导过程我们先回顾 C 程序的完整翻译步骤test.c → 预处理 → test.i → 编译 → test.s → 汇编 → test.o → 链接 → test.exe3.2 模拟完整翻译过程的 Makefile我们可以写出一个展示完整翻译过程的 Makefiletest.exe: test.o gcc -o test.exe test.o test.o: test.s gcc -c test.s -o test.o test.s: test.i gcc -S test.i -o test.s test.i: test.c gcc -E test.c -o test.i3.3 make 命令的解析过程当我们执行make命令时make 会从 Makefile 的第一个目标开始解析这里是test.exe检查目标文件是否存在以及所有依赖文件是否都是最新的如果某个依赖文件不存在或者比目标文件更新就会去查找该依赖文件的生成规则这个过程会一直持续下去直到找到一个已经存在的源文件这里是test.c然后从最底层开始依次执行依赖方法生成上一层的文件直到最终生成目标文件核心比喻这个过程就像一个栈结构先进后出或者函数递归。test.c就是递归的出口。四、项目清理与伪目标 .PHONY4.1 为什么需要清理项目一个完整的项目不仅要能构建还要能清理。清理就是删除所有生成的中间文件.i、.s、.o和最终的可执行程序。4.2 初步的清理目标我们可以在 Makefile 中添加一个clean目标clean: rm -f test.exe test.i test.s test.o执行make clean命令就会自动删除这些文件。4.3 问题出现了如果当前目录下恰好有一个名为clean的文件那么执行make clean时make 会认为clean文件已经是最新的不会执行任何命令。4.4 解决方案伪目标 .PHONY.PHONY是 Makefile 中的一个特殊关键字用来声明伪目标。语法.PHONY: clean clean: rm -f test.exe test.i test.s test.o核心结论被.PHONY修饰的目标总是会被执行无论当前目录下是否存在同名文件也不会进行时间对比。五、Makefile 高效编译的秘密文件时间对比5.1 为什么第二次 make 不会重新编译当我们第一次执行make生成test.exe后再次执行make会看到提示make: test.exe is up to date.这是因为 make 会比较源文件和目标文件的修改时间如果源文件的修改时间比目标文件晚 → 源文件被修改过需要重新编译如果目标文件的修改时间比源文件晚 → 源文件没有被修改不需要重新编译核心价值这种机制可以大大提高大型项目的编译效率。当你只修改了一个源文件时make 只会重新编译这一个文件然后重新链接而不是编译整个项目。5.2 Linux 文件的三个时间属性使用stat 文件名命令可以查看文件的详细时间信息时间属性英文全称含义AccessAccess Time最近一次访问文件内容的时间ModifyModify Time最近一次修改文件内容的时间ChangeChange Time最近一次修改文件属性的时间如权限、大小等关键区别修改文件内容 → Modify 时间和 Change 时间都会更新因为内容改变会导致文件大小等属性改变只修改文件属性如chmod命令→ 只有 Change 时间会更新5.3 为什么 Access 时间不会每次访问都更新老师提出了一个深刻的问题为什么我们多次cat同一个文件Access 时间却不一定更新原因系统中读操作的频率远远高于写操作如果每次访问文件都更新 Access 时间就意味着每次读操作都要伴随一次写磁盘操作更新文件属性磁盘 IO 是计算机系统中最慢的操作之一频繁的写磁盘会严重降低系统性能解决方案现代 Linux 系统会对 Access 时间的更新进行优化通常是每隔一段时间或累计一定次数的访问后才更新一次。六、Makefile 语法进阶6.1 禁止命令回显符号默认情况下make 会把它执行的每一条命令都打印到终端上。如果我们不想看到这些命令可以在命令前加上符号。示例test.exe: test.c echo 开始编译代码... gcc -o $ $^ echo 编译完成 .PHONY: clean clean: echo 清理工程... rm -f test.exe echo 清理完毕执行make时只会看到我们自定义的提示信息不会看到实际执行的 gcc 和 rm 命令。6.2 自定义变量当 Makefile 变得复杂时使用变量可以让代码更简洁、更易维护。定义变量BIN test.exe SRC test.c使用变量$(BIN): $(SRC) gcc -o $(BIN) $(SRC) .PHONY: clean clean: rm -f $(BIN)注意等号两侧可以有空格定义变量时等号左右两侧允许加空格并且强烈推荐加上以提高代码的可读性如BIN test.exe。千万警惕“尾随空格”致命陷阱Makefile 中的所有变量本质上都是字符串。它会自动忽略等号前后的空格但会把变量值后面的所有空格当成值的一部分例如如果写成 BIN test.exe 末尾不小心敲了几个空格那么 BIN 的实际值就是 test.exe 。这会导致后续编译器找不到对应的文件而报错。区分 Shell 脚本如果你在写 Shell 脚本.sh等号两边才是绝对不能加空格的必须写成 BINtest.exe不要和 Makefile 搞混。6.3 内置自动变量Makefile 提供了一些非常有用的内置自动变量它们会根据当前的规则自动展开自动变量含义$表示规则中的目标文件$表示规则中的第一个依赖文件$^表示规则中的所有依赖文件以空格分隔使用示例test.exe: test.c gcc -o $ $ # $ 展开为 test.exe$ 展开为 test.c七、处理多文件项目7.1 问题100个源文件怎么办如果我们的项目有100个源文件main.c、src1.c、src2.c…src100.c难道我们要在 Makefile 中把它们一个个列出来吗7.2 解决方案一使用 shell 命令获取源文件列表SRC $(shell ls *.c)$(shell 命令)会执行括号中的 shell 命令并将命令的输出结果作为变量的值。这里ls *.c会列出当前目录下所有的 .c 文件。7.3 解决方案二使用 wildcard 函数推荐Makefile 内置的wildcard函数专门用来获取符合特定模式的文件名SRC $(wildcard *.c)7.4 将 .c 后缀替换为 .o 后缀我们需要将所有的 .c 源文件编译成对应的 .o 目标文件。可以使用 Makefile 的变量替换功能OBJ $(SRC:.c.o)这行代码的意思是将SRC变量中所有以.c结尾的字符串替换为以.o结尾。7.5 模式规则%.o: %.c现在我们需要一个通用的规则告诉 make 如何将任意一个 .c 文件编译成对应的 .o 文件%.o: %.c gcc -c $ echo 编译 $ 完成这里的%是一个通配符它会匹配任意字符串。例如当 make 需要生成main.o时它会自动匹配这条规则将%替换为main然后执行gcc -c main.c。7.6 多文件项目的完整 MakefileBIN bite.exe SRC $(wildcard *.c) OBJ $(SRC:.c.o) $(BIN): $(OBJ) echo 链接所有目标文件... gcc -o $ $^ echo 生成可执行文件 $ 完成 %.o: %.c echo 编译 $ ... gcc -c $ .PHONY: clean clean: echo 清理工程... rm -f $(OBJ) $(BIN) echo 清理完毕7.6.1 符号详解1. 变量操作符号 (,$())对应行1, 2, 3, 5, 7 等(赋值号)最基本的变量赋值。如第 1 行BIN bite.exe将右边的字符串赋值给左边的变量。$()(取值/展开符)用来获取变量的值或者调用 Makefile 的内置函数。例如$(BIN)就是把BIN的值bite.exe提取出来。在 Makefile 中只要想使用变量就必须用$()包裹它单字符变量除外但建议全包。2. 内置函数与文本处理对应行2, 3wildcard(通配符函数)* 语法$(wildcard 匹配模式)解释第 2 行$(wildcard *.c)的意思是去当前目录下找所有以.c结尾的文件并把它们的名字用空格拼成一长串字符串赋值给SRC比如main.c utils.c。:.c.o(模式替换语法)语法$(变量名:原后缀新后缀)解释第 3 行$(SRC:.c.o)是一个非常巧妙的文本替换。它会把SRC变量里所有的.c结尾的字符串全部替换成.o。如果SRC是main.c utils.c那么OBJ就会自动变成main.o utils.o。3. 规则定义与模式匹配 (:,%)对应行5, 10:(规则分隔符)语法目标 : 依赖解释告诉 make左边的文件是怎么来的依赖于右边的文件。%(模式通配符)解释第 10 行%.o: %.c称为“模式规则” (Pattern Rule)。这里的%就像一个占位符Stem。含义它告诉 Make 一个通用的道理——“任何一个.o文件都依赖于和它同名的.c文件”。当 Make 需要生成main.o时它会自动套用这条规则把%替换成main去寻找main.c。这比你手动一行行写main.o: main.c要聪明得多。4. 自动变量 ($,$,$^)对应行7, 8, 11, 12$代表冒号左边的目标文件。在第 7 行里它就是bite.exe在第 12 行里它就是当时匹配到的那个.o文件。$^代表冒号右边的所有依赖文件。在第 7 行里它代表所有的.o文件。所以gcc -o $ $^实际上是在把所有.o文件链接成一个bite.exe。$代表冒号右边的第一个依赖文件。在第 12 行的编译命令gcc -c $中它代表当前正在被编译的那个.c源文件。5. 命令控制符号 ()对应行6, 7, 8, 11 等所有缩进的执行命令(静默执行符)* 解释默认情况下Make 在执行命令前会先把这行命令原原本本地打印到屏幕上。如果在命令前面加上Make 就只会执行命令而不会在屏幕上回显命令本身。作用让终端输出更干净。比如你只想看到echo打印出来的中文提示语而不想看到系统把echo 链接所有目标文件...这句代码本身也打印一遍。6. 特殊伪目标 (.PHONY)对应行14.PHONY(声明伪目标)解释第 14 行.PHONY: clean告诉 Makeclean不是一个真正的文件名它只是一个“动作的代号”Pseudo-target。为什么要加假设你的目录下刚好新建了一个叫clean的文件如果你没加.PHONY当你敲make clean时Make 会发现“咦clean文件已经存在了且没有依赖项需要更新”它就会罢工不再执行下面的删除命令。加上.PHONY后无论有没有同名文件Make 都会强制执行clean标签下的命令。总结这个 Makefile 的工作流收集当前所有的.c文件第 2 行。推导出需要生成的.o文件列表第 3 行。发现终极目标bite.exe需要所有的.o文件第 5 行。于是自动利用模式规则第 10 行把一个个.c编译成.o。所有.o都准备好后把它们链接成最终的bite.exe第 7 行。7.6.2 增量编译疑问为什么不能像链接那样把所有.c一次性全编译了这个想法在底层是可以实现的。gcc确实支持你一口气传入所有的源文件比如执行gcc -c main.c utils.c它也会乖乖吐出main.o和utils.o。但是如果在 Makefile 里这么写就完全毁了 Makefile 的“灵魂”Makefile 存在的最大意义叫作增量编译Incremental Build。如果按照这个设想写成“全包”的形式# 假设我们这么写反面教材 $(OBJ): $(SRC) echo 一次性编译所有文件... gcc -c $^ # 把所有 .c 一起喂给 gcc致命后果假设你的项目有 1000 个.c文件。今天你只修改了其中1 个文件比如utils.c的一行代码。当你敲下make时由于所有文件被绑在了一起Make 会把这 1000 个文件全部重新编译一遍本来 0.1 秒能搞定的事你要等 5 分钟。现在的写法分离式%.o: %.cMake 为每一个.o建立了独立的依赖关系。当你只修改了utils.c时Make 会检查时间戳main.c比main.o旧 - 不需要重新编译。utils.c比utils.o新因为你刚改了它 -只触发utils.o: utils.c这一条规则只重新编译utils.c。最后再把现成的main.o和刚生成的utils.o重新链接成.exe。总结链接打包必须大家一起上$^但编译加工必须拆开单干$就是为了“谁改了就只编译谁”极大提高大型项目的编译速度。八、通用 Makefile 最佳实践8.1 进一步变量化为了让 Makefile 更加通用我们可以把编译器、命令等也都变量化BIN bite.exe SRC $(wildcard *.c) OBJ $(SRC:.c.o) # 编译器 CC gcc # 回显命令 ECHO echo # 删除命令 RM rm -rf $(BIN): $(OBJ) $(ECHO) linking $^ to $ ... done $(CC) -o $ $^ %.o: %.c $(ECHO) compiling $ to $ ... done $(CC) -c $ .PHONY: clean clean: $(ECHO) cleaning project ... $(RM) $(OBJ) $(BIN) $(ECHO) clean done!8.2 这个通用 Makefile 的优势高度通用将这个 Makefile 复制到任何 C 语言项目目录下基本都能直接使用易于修改如果需要切换到 C 编译器只需将CC gcc改为CC g清晰易读所有的配置都集中在文件开头一目了然高效编译只重新编译被修改过的源文件8.3 调试技巧添加 test 目标在编写 Makefile 的过程中我们经常需要查看变量的值是否正确。可以添加一个test伪目标来帮助调试.PHONY: test test: echo SRC $(SRC) echo OBJ $(OBJ)执行make test命令就可以看到SRC和OBJ变量的实际值。

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

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

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…