【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完成了。

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

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

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

相关文章

LDAP从入门到实战:环境部署与配置指南(上)

#作者&#xff1a;朱雷 文章目录 一、LDAP 简介1.1. 什么是目录服务1.2. 什么是 LDAP1.3. LDAP的基本模型 二、Ldap环境部署2.1.下载软件包2.2.安装软件2.3.编辑配置文件2.4.启动服务 一、LDAP 简介 1.1. 什么是目录服务 目录是专门为搜索和浏览而设计的专用数据库&#xff…

《C++智能指针:建议使用 make_shared 代替 shared_ptr》

《C 智能指针&#xff1a;长达数十年的血泪史&#xff0c;一步步征服内存泄漏》-CSDN博客 shared_ptr<int> sp1(new int(10)); 这句代码实际存在两个内存开辟&#xff0c;一是开辟我们要托管的内存资源 &#xff0c;二是开辟引用计数的资源&#xff0c;引用技术也是new出…

代码随想录刷题day50|(回溯算法篇)131.分割回文串▲

目录 一、回溯算法基础知识 二、分割回文串思路 2.1 如何切割 2.2 判断回文 2.3 回溯三部曲 2.4 其他问题 三、相关算法题目 四、总结 一、回溯算法基础知识 详见&#xff1a;代码随想录刷题day46|&#xff08;回溯算法篇&#xff09;77.组合-CSDN博客 二、分割回文…

vivo 湖仓架构的性能提升之旅

作者&#xff1a;郭小龙 vivo互联网 大数据高级研发工程师 导读&#xff1a;本文整理自 vivo互联网 大数据高级研发工程师 郭小龙 在 StarRocks 年度峰会上的分享&#xff0c;聚焦 vivo 大数据多维分析面临的挑战、StarRocks 落地方案及应用收益。 在 即席分析 场景&#xff0c…

Springboot的jak安装与配置教程

目录 Windows系统 macOS系统 Linux系统 Windows系统 下载JDK&#xff1a; 访问Oracle官网或其他JDK提供商网站&#xff0c;下载适合Windows系统的JDK版本。网站地址&#xff1a;Oracle 甲骨文中国 | 云应用和云平台点击进入下滑&#xff0c;点击进入下载根据自己的系统选择&…

力扣算法Hot100——128. 最长连续序列

题目要求时间复杂度为O(n)&#xff0c;因此不能使用两次循环匹配。 首先使用 HashSet 去重&#xff0c;并且 HashSet 查找一个数的复杂度为O(1)外循环还是遍历set集合&#xff0c;里面一重循环需要添加判断&#xff0c;这样才不会达到O( n 2 n^2 n2)判断是否进入最长序列查找循…

BlockChain.java

BlockChain 区块链&#xff0c;举个栗子 注意啦&#xff0c;列子里面的hashcode相等&#xff0c;但是字符串是不一样的哦&#xff0c;之前有记录这个问题 String.hashCode()-CSDN博客

visual studio 中导入 benchmark

法一 1.visual studio 中导入 benchmark.lib Shlwapi.lib这两个库 2.预处理宏 BENCHMARK_STATIC_DEFINE vs导入参考 错误提示 没有加入 BENCHMARK STATIC_DEFINE error LNK2001: 无法解析的外部符号 “__declspec(dllimport) int __cdecl benchmark::internal::InitializeS…

java基础之windows电脑基础命令

windows电脑基础命令 windows电脑基础命令快捷键和功能键键盘功能键B:键盘快捷键 DOS命令行的进入方式xp下如何打开DOS控制台&#xff1f;win7下如何打开DOS控制台&#xff1f;win8下如何打开DOS控制台 DOS命令讲解 黑窗口编译文件使用黑窗口运行java程序 windows电脑基础命令 …

Java 第十一章 GUI编程(3)

目录 内部类 内部类定义 内部类的特点 匿名内部类 格式&#xff1a; 内部类的意义 实例 内部类 ● 把类定义在另一个类的内部&#xff0c;该类就被称为内部类。 ● 如果在类 Outer 的内部再定义一个类 Inner&#xff0c;此时类 Inner 就称为内部类 &#xff08;或称为嵌…

uniapp 实现的下拉菜单组件

采用 uniapp 实现, 是一款具备丝滑折叠、展开动画的下拉菜单&#xff0c;支持 vue2、vue3&#xff1b;适配 web、H5、微信小程序&#xff08;其他平台小程序未测试过&#xff0c;可自行尝试&#xff09; 可到插件市场下载尝试&#xff1a; https://ext.dcloud.net.cn/plugin?i…

【一维前缀和与二维前缀和(简单版dp)】

1.前缀和模板 一维前缀和模板 1.暴力解法 要求哪段区间&#xff0c;我就直接遍历那段区间求和。 时间复杂度O(n*q) 2.前缀和 ------ 快速求出数组中某一个连续区间的和。 1&#xff09;预处理一个前缀和数组 这个前缀和数组设定为dp&#xff0c;dp[i]表示&#xff1a;表示…

ubuntu部署运行xinference全精度对话deepseek本地部署图文教程

前置环境搭建劳请移步往期 source activate 自己环境名启动python3.12环境安装xinference&#xff0c; 按教程敲命令&#xff0c;wheel包与wsl的通用&#xff0c;pip install 包名。 vllm引擎&#xff0c;transform引擎也会顺带自动装上了。 后续操作请参照往期教程。本地部署模…

Python 面向对象三大特性深度解析

一、封装&#xff08;Encapsulation&#xff09; 1. 私有化实现 class BankAccount:def __init__(self, account_holder, balance0):self.__holder account_holder # 双下划线私有属性self.__balance balance# 公有方法访问私有属性def deposit(self, amount):if amount &…

星越L_陡坡缓降使用讲解

目录 1.陡坡缓降 1.陡坡缓降 中控屏下滑-点击陡坡缓降功能 35km/h以下时生效。35km/h-60km/h该功能暂停 60km/h以上该功能关闭

多路FM调频广播解调器:多路电台FM广播信号一体化解调处理方案

多路FM调频广播解调器&#xff1a;多路电台FM广播信号一体化解调处理方案 支持OEM型号开放式协议支持二次开发设计 北京海特伟业科技有限公司任洪卓发布于2025年3月21日 在信息传播领域&#xff0c;FM调频广播媒体以其独特的优势持续发挥着重要作用。为了应对日益增长的多路…

报错 - redis - Unit redis.service could not be found.

报错&#xff1a; Unit redis.service could not be found.Could not connect to Redis at 127.0.0.1:6379: Connection refused解决方法&#xff1a; 检查状态、有必要的话 重新安装 Linux 上查看状态 systemctl status redis显示以下内容&#xff0c;代表正常服务 出现下面…

Guava:Google开源的Java工具库,太强大了

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

Pytorch中layernorm实现详解

平时我们在编写神经网络时&#xff0c;经常会用到layernorm这个函数来加快网络的收敛速度。那layernorm到底在哪个维度上进行归一化的呢&#xff1f; 一、问题描述 首先借用知乎上的一张图&#xff0c;原文写的也非常好&#xff0c;大家有空可以去阅读一下&#xff0c;链接放…

六十天前端强化训练之第二十五天之组件生命周期大师级详解(Vue3 Composition API 版)

欢迎来到编程星辰海的博客讲解 看完可以给一个免费的三连吗&#xff0c;谢谢大佬&#xff01; 目录 一、生命周期核心知识 1.1 生命周期全景图 1.2 生命周期钩子详解 1.2.1 初始化阶段 1.2.2 挂载阶段 1.2.3 更新阶段 1.2.4 卸载阶段 1.3 生命周期执行顺序 1.4 父子组…