【Linux】Makefile秘籍

news2025/3/21 22:44:47

 > 🍃 本系列为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 100

int main()
{
    printf("hello Linux,%d\n",N);


#ifdef DEBUG_MODE
printf("Debug mode is enabled.\n");
#else
    printf("Debug mode is disabled.\n");
#endif

#ifndef DEBUG_MODE
    printf("This code is only compiled if DEBUG_MODE is not defined.\n");
#endif

    // 条件编译:根据变量值选择性编译
#if x > 5
    printf("x is greater than 5.\n");
#elif x == 5
    printf("x is equal to 5.\n");
#else
    printf("x is less than 5.\n");
#endif
    return 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.c
	gcc 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.o
	gcc code.o -o code.exe
code.o:code.s
	gcc -c code.s -o code.o
code.s:code.i
	gcc -S code.i -o code.s
code.i:code.c
	gcc -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.o
	gcc code.o -o code.exe
code.o:code.c
	gcc -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.coloradmin.cn/o/2319207.html

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

相关文章

玩转物联网-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 发布信…

vue3+ts心得

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

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

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

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

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

vscode查看文件历史git commit记录

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

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…

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

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

深入理解traceroute命令及其原理

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

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

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

【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;…

【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;笔者安…

Web3 时代数据保护的关键挑战与应对策略

Web3 时代数据保护的关键挑战与应对策略 随着互联网技术的飞速发展&#xff0c;我们正步入 Web3 时代&#xff0c;这是一个以去中心化、用户主权和数据隐私为核心的新时代。在这个时代&#xff0c;数据保护成为了一个至关重要的议题。本文将探讨 Web3 时代数据保护面临的主要挑…

SpringBoot之如何集成SpringDoc最详细文档

文章目录 一、概念解释1、OpenAPI2、Swagger3、Springfox4、Springdoc5. 关系与区别 二、SpringDoc基本使用1、导包2、正常编写代码&#xff0c;不需要任何注解3、运行后访问下面的链接即可 三、SpringDoc进阶使用1、配置文档信息2、配置文档分组3、springdoc的配置参数**1. 基…

【智能体】| 知识库、RAG概念区分以及智能体是什么

文章目录 前言简介大模型“幻觉”问题如何解决“幻觉”问题&#xff1f; RAG、智能体、RAG智能体概念什么是检索增强型生成&#xff08;RAG&#xff09;模拟简单的RAG场景 AI系统中的智能体是什么什么是Agentic RAG&#xff1f;Agentic RAG如何工作&#xff1f;Agentic RAG架构…

二分查找的应用

什么时候用二分查找&#xff1f; 数据具有二段性的时候 第一题&#xff1a; 题解代码&#xff1a; class Solution { public:int search(vector<int>& nums, int target) {int left 0,right nums.size()-1;while(left<right){int mid left (right-left)/2;//中…