当前位置: 首页 > article >正文

【Linux】Makefile秘籍

 > 🍃 本系列为Linux的内容,如果感兴趣,欢迎订阅🚩

> 🎊个人主页:【小编的个人主页

>小编将在这里分享学习Linux的心路历程✨和知识分享🔍

>如果本篇文章有问题,还请多多包涵!🙏
>  🎀   🎉欢迎大家点赞👍收藏⭐文章

> ✌️ 🤞 🤟 🤘 🤙 👈 👉 👆 🖕 👇 ☝️ 👍


目录

  

🐼前言 

   🐼预处理、编译、汇编和链接都在干什么??

   🐼为什么推荐使用Makefile???

   🐼make原理

 🐼make最佳实践


  

🐼前言 

一个可执行程序的生成通常需要经过预处理、编译、汇编和链接四个主要阶段。在 Linux 中,对于简单的单文件项目,我们常常可以直接使用 gcc code.c -o code 一步到位地生成可执行程序,然而,如果我们反复执行这条命令,依旧会编译,这就极大的浪费了时间和空间。对于大型项目,情况就复杂得多。大型项目通常包含多个 .c 文件、头文件以及其他资源文件。在这种情况下,手动管理这些文件的编译和链接过程不仅繁琐,而且容易出错。

为了简化大型项目的构建过程,Linux 引入了 make 工具。make 是一个强大的自动化构建工具,它可以根据项目中的文件依赖关系,自动执行预处理、编译、汇编和链接等步骤,生成最终的可执行程序。但要使用 make,我们需要在项目目录下编写一个 Makefile 文件,它定义了项目的构建规则。

因此,本篇博客将分享一个比较完善的 Makefile 编写模板,帮助大家更好地理解和使用 Makefile 来构建大型项目。

   🐼预处理、编译、汇编和链接都在干什么??

预处理:进行宏替换,去注释,条件编译替换,头文件展开

如果我们当前目录已有code.c这个文件,code.c内容是:

#include<stdio.h>#define N 100int main()
{printf("hello Linux,%d\n",N);#ifdef DEBUG_MODE
printf("Debug mode is enabled.\n");
#elseprintf("Debug mode is disabled.\n");
#endif#ifndef DEBUG_MODEprintf("This code is only compiled if DEBUG_MODE is not defined.\n");
#endif// 条件编译:根据变量值选择性编译
#if x > 5printf("x is greater than 5.\n");
#elif x == 5printf("x is equal to 5.\n");
#elseprintf("x is less than 5.\n");
#endifreturn 0;
}

我们执行gcc -E code.c -o code.i形成预处理后的.i文件(其中-o后是目标文件)

在我们目录下生成了,code.i文件,code.i其内容是

头文件展开:

预处理后确实发生进行宏替换,去注释,条件编译替换,头文件展开

头文件展开是将头文件的内容,拷贝到我们code.c中

条件编译和注释,本质是对代码的裁剪。

因此,在预处理之后,我们可以不需要在使用头文件了。

编译:形成汇编语言

我们使用gcc -S code.i -o code.s形成编译后的汇编文件code.s,这条指令的意思是从预处理文件开始,到编译完成就结束

code.s其内容是:

我们发现,确实是汇编语言。

我们再观察当前目录所有文件:

下面,我们使用gcc -c code.s -o code.o形成可重定位目标二进制文件。(也就是计算器认识的语言,二进制代码)。code.o

我们发现是一堆乱码。

我们尝试运行一下。

现在有个问题,为什么我们明明都已经形成了机器读懂的二进制文件,为什么还是运行不了。原因是我们所写的二进制文件,只是我们写的,并没有链接一些库,导致编译器不认识一些库函数(printf)等,只有和库库文件链接,才形成真的可执行程序。

因此,最后一步,我们进行和库链接。执行

使用命令:gcc code.o -o code.exe

程序被执行出来了!!!!!

   🐼为什么推荐使用Makefile???

 那我们既然能在命令行手动执行命令输出我们想到的程序,那我们为什么还要专门自已写一个Makefile,再使用make命令运行我们的可制成程序???

对于文件个数不多,我们还能处理,并且我们只要给计算机下达帮我们编译的命令,就能帮我们编译。那如果文件个数很多,比如有1000个.c文件,10个头文件等等,我们在通过命令行的方式手动编译,显得不现实,并且,我们已经有了可执行程序,我们再使用gcc code.o -o code.exe,编译器依旧会执行。

这点很致命,浪费了时间,效率。并且,我们也不好清理。因此,我们推荐使用Makefile根据项目中的文件依赖关系,自动执行预处理、编译、汇编和链接等步骤,生成最终的可执行程序并且完成清理工作。

   🐼make原理

make是Linux中一个内置命令

makefile是我们需要自已手动新建的文件

如果当前目录已经有了makefile文件,我们可以直接使用make命令

下面我们编写一个简单的makefile(我们直接通过.c生成可执行程序)

code.exe:code.cgcc code.c -o code.exe.PHONY:clean
clean:rm -rf code.exe 

其中code.exe,我们叫做目标文件,code.c是code.exe的依赖关系。打个比方,想要踢足球,就必须要有足球。

gcc code.c -o code.exe我们叫做依赖方法。其前面必须又空一个Tab键

关键字.PHONY后面修饰的目标是一个伪目标,而伪目标总是被执行。

那有个问题,什么叫做总是被执行????

而为什么第一个就不修饰成伪目标???

总是被执行,指的是,只要我们通过命令(make clean)执行,他都会被执行。而不是伪目标的,只会默认形成一个目标,就是从上向下的第一个目标,并且只会被执行一次,这里是make形成可执行程序,只会形成一次。

可是它是怎么知道make形成的可执行程序只执行一次,而.PHONY修饰的文件,总是被执行???

我们知道,Linux一切皆文件,文件=文件内容+文件属性。

系统知道该文件最近修改时间,因此可以对比.src和.exe文件修改时间。来决定是否需要重新编译。只要.exe文件比.src更新,就不修改。反之

而.PHONY无视修改时间,总是执行。

因此我们可以理解,下面这个现象:

我们再make,发现不让我make了。这就解释了,make只会从Makefile从上到下扫描第一个目标文件,并且只会执行一次。系统通过比对.src的修改时间,如果该文件没被修改之前,make无法执行,也就只会编译一次。而我们不希望,文件忘记清理。

最佳实践,可执行程序不要被修饰成.PHONY,而clean,我们希望修饰成.PHONY,总是被执行

这样让我们的编译效率尽可能高一点。

 🐼make最佳实践

下面我们编写几个Makefile,最后,引入我们通用的Makefile版本

既然是从Makefile从上往下依次找依赖关系code.exe依赖code.o依赖code.s依赖code.i依赖code.c。并且形成可执行程序要四步那Makefile先这么写;

code.exe:code.ogcc code.o -o code.exe
code.o:code.sgcc -c code.s -o code.o
code.s:code.igcc -S code.i -o code.s
code.i:code.cgcc -E code.c -o code.i.PHONY:clean
clean:rm -rf code.exe code.i code.o code.s 

使用命令make

形成一系列文件

调用make clean完成清理

其实:我们的习惯都是,先直接让单个程序形成单个的obj文件然后停下,然后再将所有obj文件链接。因此我们可以这么写;

code.exe:code.ogcc code.o -o code.exe
code.o:code.cgcc -c code.c
.PHONY:clean
clean:rm -rf code.exe code.o

但是这样写还是不够普遍性,对于文件名不同,改起来麻烦。所以,在Makefile,引入了$().

其中()中是变量名,可以是文件。因此,为了方便替换,我们可以这么写:

BIN=code.exe
SRC=code.c
OBJ=code.o
CC=gcc
LFLAGS=-o
CFLAGS=-c
RM = rm -rf$(BIN):$(OBJ)$(CC) $(LFLAGS) $(BIN) $(OBJ)
$(OBJ):$(SRC)$(CC) $(CFLAGS) $(SRC) $(LFLAGS) $(OBJ).PHONY:clean
clean:$(RM) $(BIN) $(OBJ)	

这样我们通过替换,可以直接修改文件名,以及依赖方法等。

并且,为了方便,我们可以直接将依赖方法中的$(BIN)换成$@ ,$(OBJ)换成$^

即将目标文件,换成@;依赖关系换成^

BIN=code.exe
SRC=code.c
OBJ=code.o
CC=gcc
LFLAGS=-o
CFLAGS=-c
RM = rm -rf$(BIN):$(OBJ)$(CC) $(LFLAGS) $@ $^
$(OBJ):$(SRC)$(CC) $(CFLAGS) $(SRC) .PHONY:clean
clean:$(RM) $(BIN) $(OBJ)	

但是我们这里只针对一个文件,当有多个文件时,我们希望可以处理所有的.c->.o文件 && .o->.exe文件,可以这么写:

对于所有的源文件

SRC=$(wildcard *.c)使用函数 wildcard 来查找当前目录下所有的 .c 文件,并将这些文件的列表赋值给变量 SRC

OBJ=$(SRC: .c=.o)将 SRC 变量中的每个 .c 文件扩展名替换为 .o,生成对象文件列表,赋值给变量 OBJ

%.o:%.c:定义了一个通用规则,目标是任意 .o 文件,依赖是对应的 .c 文件。规则的命令是 $(CC) $(CFLAGS) $<,这意味着使用 gcc 编译每个 .c 文件生成对应的 .o 文件

BIN=code.exe
SRC=$(wildcard: *.c)
OBJ=$(SRC:.c=.o)
CC=gcc
LFLAGS=-o
CFLAGS=-c
RM =rm -rf$(BIN):$(OBJ)$(CC) $(LFLAGS) $@ $^
%.o:%.c$(CC) $(CFLAGS) $<.PHONY:clean
clean:@$(RM) $(BIN) $(OBJ)	.PHONY:print
print:@echo $(SRC)@echo "--------------------------" @echo $(OBJ)

最后,为了完善Makefile,我们加入一些提示词,并使用@来过滤掉命令提示!

最佳实践

BIN=code.exe
SRC=$(wildcard *.c)
OBJ=$(SRC:.c=.o)
CC=gcc
LFLAGS=-o
CFLAGS=-c
RM =rm -rf$(BIN):$(OBJ)@$(CC) $(LFLAGS) $@ $^@echo "编译$^成为$@"
%.o:%.c@$(CC) $(CFLAGS) $<@echo "链接$<成为$@".PHONY:clean
clean:@$(RM) $(BIN) $(OBJ)@echo "清理完成".PHONY:print
print:@echo $(SRC)@echo "--------------------------" @echo $(OBJ)

为了测试:我们使用touch file{1..100}.c当前目录下创建100个空文件。

调用make命令:

最终编译101个.o文件形成code.exe可执行文件。

最后我们使用./code.exe,执行:

我们通过make指令执行自动化编译过程,Makefile允许定义一组规则来指定如何编译和链接程序。这样,我们一个比较完整的Makefile完成了。

   感谢你耐心地阅读到这里,你的支持是我不断前行的最大动力。如果你觉得这篇文章对你有所启发,哪怕只是一点点,那就请不吝点赞👍,收藏⭐️,关注🚩吧!你的每一个点赞都是对我最大的鼓励,每一次收藏都是对我努力的认可,每一次关注都是对我持续创作的鞭策。希望我的文字能为你带来更多的价值,也希望我们能在这个充满知识与灵感的旅程中,共同成长,一起进步。如果本篇文章有错误,还请大佬多多指正,再次感谢你的陪伴,期待与你在未来的文章中再次相遇!⛅️🌈 ☀️    

相关文章:

【Linux】Makefile秘籍

> &#x1f343; 本系列为Linux的内容&#xff0c;如果感兴趣&#xff0c;欢迎订阅&#x1f6a9; > &#x1f38a;个人主页:【小编的个人主页】 >小编将在这里分享学习Linux的心路历程✨和知识分享&#x1f50d; >如果本篇文章有问题&#xff0c;还请多多包涵&a…...

玩转物联网-4G模块如何快速将数据上传到巴法云(TCP篇)

目录 1 前言 2 环境搭建 2.1 硬件准备 2.2 软件准备 2.3 硬件连接 2.4 检查驱动 3 巴法云平台设备创建 3.1 创建账号 3.2 进入巴法云 3.3 获取联网参数 4 连接巴法云 4.1 打开配置工具读取基本信息 4.2 设置连接参数进行数据交互 4.2.1 建立TCP连接 4.2.2 订阅主题 4.2.3 发布信…...

postgresql 高版本pgsql备份在低版本pgsql中恢复失败,报错:“unsupported version”

关键字 PostgreSQL、pg_restore、版本兼容性、数据库迁移、pg_dump、备份恢复、unsupported version in file header 背景环境 系统配置 环境类型操作系统PostgreSQL版本内存工具链测试环境Windows 111616GBNavicat/PgAdmin生产环境Windows Server 2012 R2128GBPgAdmin/命令…...

html相关常用语法

html相关常用语法 HTML&#xff08;HyperText Markup Language&#xff09;即超文本标记语言&#xff0c;是用于创建网页的标准标记语言 HTML使用标记语言描述Web页面的结构 HTML元素是HTML页面的建构快 HTML元素通过标签tag来表示 HTML标签是“标题”、”段落“、”表格“等内…...

vue3+ts心得

1、Vue3核心 1、setup setup里弱化this&#xff0c;return可以返回函数&#xff0c;返回后页面也显示那个函数值 data里面是可以用this.来获取setup里的值&#xff0c;这个是单向的 vue3两个script标签不要觉得奇怪&#xff0c;一个是配置组合式api的&#xff0c;一个是配置组…...

单片机flash存储也做磨损均衡

最近在做一个项目&#xff0c;需要保存设置数据&#xff0c;掉电不丢失。那么首先想到的是加个24c02&#xff0c;是一个eeprom&#xff0c;但是客户板太小&#xff0c;没有办法进行扩展。后面就找了一个带ee的OTP单片机&#xff0c;发现擦写次数有限&#xff0c;只有1000次&…...

【FAQ】HarmonyOS SDK 闭源开放能力 —Push Kit(10)

1.问题描述&#xff1a; 离线推送&#xff0c;锁屏的时候没有弹出消息&#xff0c;只有下拉在通知中心里面显示。请问是否是正常的&#xff1f; 解决方案&#xff1a; 检查一下是否存在图片风控&#xff1a;https://developer.huawei.com/consumer/cn/doc/harmonyos-referen…...

SQLark中如何进行数据筛选与排序

本文将为你介绍在 SQLark 中如何进行数据筛选与排序&#xff0c;掌握这些操作能够极大提升你的工作效率。 SQLark官网链接:www.sqlark.com 数据筛选 在数据库操作中&#xff0c;数据筛选是一项关键功能&#xff0c;它依据特定条件对数据进行过滤&#xff0c;帮助用户从海量数据…...

token升级(考虑在分布式环境中布置token,结合session保证请求调用过程中token不会过期。)

思路&#xff1a; 首先&#xff0c;用户的需求是确保使用同一个Token的外部调用都在一个Session中处理。 需要考虑Token与Session绑定、安全措施、Session管理、分布式处理等。 使用Redis作为Session存储&#xff0c; 在Java中 通过Spring Data Redis或Lettuce库实现。 2.生成…...

VSTO(C#)Excel开发11:自定义任务窗格与多个工作簿

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 源码指引&#xff1a;github源…...

python:AI+ music21 构建LSTM模型生成爵士风格音乐

这是一个使用 python的 music21 和 TensorFlow/Keras 构建 LSTM 模型生成爵士风格音乐的完整脚本。该脚本包含MIDI数据处理、模型训练和音乐生成全流程&#xff1a; # -*- coding: utf-8 -*- """AI music21 和 TensorFlow/Keras 构建LSTM模型生成爵士风格音乐 …...

vscode查看文件历史git commit记录

方案一&#xff1a;GitLens 在vscode扩展商店下载GitLens 选中要查看的文件&#xff0c;vscode界面右上角点击GitLens的图标&#xff0c;选择Toggle File Blame 界面显示当前打开文件的所有修改历史记录 鼠标放到某条记录上&#xff0c;可以看到记录详情&#xff0c;选中O…...

使用netDxf扩充LaserGRBL使它支持Dxf文件格式

为 LaserGRBL 扩展支持 DXF 文件格式&#xff0c;需要了解 LaserGRBL 的代码结构&#xff0c;并在其基础上添加 DXF 文件的解析和转换逻辑。以下是详细的扩展方案&#xff1a; 1. 了解 LaserGRBL LaserGRBL 是一个用于控制激光雕刻机的开源软件&#xff0c;支持 G 代码文件的加…...

GaussDB备份数据常用命令

1、常用备份命令gs_dump 说明&#xff1a;是一个服务器端工具&#xff0c;可以在线导出数据库的数据&#xff0c;这些数据包含整个数据库或数据库中指定的对象&#xff08;如&#xff1a;模式&#xff0c;表&#xff0c;视图等&#xff09;&#xff0c;并且支持导出完整一致的数…...

数学建模 第三节

目录 前言 一 钻井布局问题 第一问分析 第二问分析 总结 前言 这里讲述99年的钻井布局问题&#xff0c;利用这个问题讲述模型优化&#xff0c;LINGO&#xff0c;MATLAB的使用 一 钻井布局问题 这个是钻井布局的原题&#xff0c;坐标的位置为 a [0.50,1.41,3.00,3.37,3…...

单调队列【C/C++】

当我在网上搜索了一大堆单调队列的文章后&#xff0c; 我人傻了&#xff01;&#xff1f; 单调队列不应该很难吗&#xff1f;&#xff1f; 不应该是&#xff0c;像 优先队列 那样&#xff0c;站在 堆 的肩膀上&#xff0c;极尽升华吗&#xff1f;&#xff1f;&#xff1f; …...

Spark DataFrame、Dataset 和 SQL 解析原理深入解析(万字长文多张原理图)

目录 1. Spark SQL 概述 1.1 Spark 整体架构概览 1.2 DataFrame 与 Dataset 简介 2. RDD 与 Spark 的分布式架构基础 2.1 弹性分布式数据集(RDD) 2.2 Spark 的分布式执行模型 3. SQL 解析流程与 Catalyst 优化器 3.1 SQL 解析流程概述 3.2 解析阶段与抽象语法树(AST…...

算法系列——有监督学习——3.逻辑回归

一、概述 逻辑回归是一种学习某个事件发生概率的算法。利用这个概率&#xff0c;可以对某个事件发生或不发生进行二元分类。虽然逻辑回归本来是二元分类的算法&#xff0c;但也可以用于三种类别以上的分类问题。为了理解这个算法&#xff0c;请思考以下例子。 你在回家的路上发…...

深入理解traceroute命令及其原理

traceroute 是一个网络诊断工具&#xff08;Windows上叫tracert&#xff09;&#xff0c;用于显示数据包从本地主机到远程主机经过的路由&#xff08;跳数&#xff09;。它可以帮助您了解数据包在网络中的传输路径&#xff0c;以及每跳的延迟情况。这对于网络故障排除、分析网络…...

前后端联调解决跨域问题的方案

引言 在前后端分离的开发模式中&#xff0c;前端和后端通常在不同的服务器或端口运行&#xff0c;这样就会面临跨域问题。跨域问题是指浏览器因安全限制阻止前端代码访问与当前网页源不同的域、协议或端口的资源。对于 Java 后端应用&#xff0c;我们可以通过配置 CORS&#x…...

深入解析 .NET 中的依赖项加载机制:原理、实现与最佳实践

在现代应用程序的开发中&#xff0c;依赖项管理与加载是非常重要的组成部分&#xff0c;尤其是在大型系统中&#xff0c;如何高效地加载和管理依赖项可以极大地影响应用程序的性能、可维护性和扩展性。在 .NET 中&#xff0c;依赖项加载不仅涉及静态依赖的管理&#xff0c;还包…...

【vue2 + Cesium】相机视角移动+添加模型、模型点击事件

参考文章&#xff1a;vue2 使用 cesium 【第二篇-相机视角移动添加模型】 这篇文章在上篇文章的基础上继续开发&#xff0c;主要实现效果 相机视角移动 添加模型 点击事件 上篇文章&#xff1a;【vue2 Cesium】使用Cesium、添加第三方地图、去掉商标、Cesium基础配置、地…...

【AI】AI编程助手:Cursor、Codeium、GitHub Copilot、Roo Cline、Tabnine

文章目录 一、基本特性对比二、收费标准三、私有部署能力1、Tabnine2、Roo Code 三、代码补全与自然语言生成代码四、安装独立的IDE安装插件安装 五、基本使用&#xff08;一&#xff09;Cursor&#xff08;二&#xff09;GitHub Copilot1、获取代码建议2.聊天1&#xff09;上下…...

我的uniapp自定义模板

uniapp自定义模板 如有纰漏请谅解&#xff0c;以官方文档为准后面这段时间我会学习小程序开发的知识&#xff0c;会持续更新可以查看我的github&#xff0c;后续我会上传我的uniapp相关练习代码有兴趣的话可以浏览我的个人网站&#xff0c;我会在上面持续更新内容&#xff0c;…...

如何将MediaPipe编译成Android中Chaquopy插件可用的 .whl 文件

环境准备 操作系统&#xff1a;建议使用 Ubuntu 20.04 或者 macOS&#xff08;这篇博客会以 Ubuntu 为例&#xff09;。Python 版本&#xff1a;Python 3.7 或以上版本。Android Studio&#xff1a;配置好 Android Studio 和 Android NDK&#xff08;Native Development Kit&a…...

华为OD机试-绘图机器-双指针(Java 2025 A卷 100分)

题目描述 绘图机器的绘图笔初始位置在原点 (0, 0)。机器启动后按照以下规则绘制直线: 尝试沿着横坐标正向绘制直线,直到给定的终点 E。期间可以通过指令在纵坐标轴方向进行偏移,offsetY 为正数表示正向偏移,为负数表示负向偏移。给定的横坐标终点值 E 以及若干条绘制指令,…...

【C++】动态规划从入门到精通

一、动态规划基础概念详解 什么是动态规划 动态规划&#xff08;Dynamic Programming&#xff0c;DP&#xff09;是一种通过将复杂问题分解为重叠子问题&#xff0c;并存储子问题解以避免重复计算的优化算法。它适用于具有以下两个关键性质的问题&#xff1a; 最优子结构&…...

OpenCV计算摄影学(23)艺术化风格化处理函数stylization()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 风格化的目的是生成不以照片写实为目标的多种多样数字图像效果。边缘感知滤波器是风格化处理的理想选择&#xff0c;因为它们能够弱化低对比度区…...

S32K144外设实验(三):ADC单通道连续采样(中断)

这次的实验比较简单&#xff0c;主要目的就是验证一下ADC的中断功能&#xff0c;思路是使用软件触发ADC的连续单通道采样&#xff0c;将采样值通过串口发送到上位机观察数是否正确。 其实官方并不推荐使用中断的方式&#xff0c;这种方式会占用大量的CPU资源&#xff0c;笔者安…...

LeetCode 第19~21题

LeetCode 第19题&#xff1a;删除链表的倒数第N个结点 题目描述 给你一个链表&#xff0c;删除链表的倒数第n个结点&#xff0c;并且返回链表的头结点。 难度&#xff1a;中等 题目链接&#xff1a;19. 删除链表的倒数第 N 个结点 - 力扣&#xff08;LeetCode&#xff09; 示例…...