【Linux】项目自动化构建工具-make与Makefile的简单使用(模拟实现进度条)

news2024/11/22 11:29:44

目  录

  • 1 make与Makefile使用
  • 2 模拟实现进度条


前言:

  • 会不会编写Makefile,从侧面说明了一个人是否具备完成大型工程的能力。
  • 一个工程中的源文件不计其数,按类型、功能、模块分别放在若干个目录中,Makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。
  • Makefile的好处在于自动化编译,一旦编写好Makefile文件,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
  • make是一个用于解释Makefile中指令的命令工具,通常大多数的IDE都有这个命令,如:Delphi的make,Visual C++的nmake,Linux下GUN的make。 可见,Makefile成为了一种在工程方面的编译方法。
  • make是一条命令,Makefile是一个文件,二者搭配使用以完成项目自动化构建。

1 make与Makefile使用

首先以对 test.c 文件的编译为例来了解make与Makefile的基本使用:

make与Makefile使用


Makefile的基本编写规则:

  • Makefile文件中保存了编译器和连接器的参数选项,并且描述了所有源文件之间的关系。make程序会读取Makefile文件中的数据,然后根据文件中编写的相关指令调用编译器、汇编器、链接器以产生最后的输出。
  • Makefile文件中包括了 依赖关系依赖方法 。如上图所示,我们以 目标文件:依赖文件 的格式来编写依赖关系,表示要生成目标文件,需要依赖有对应的依赖文件。在图例中即是:test文件(可执行文件)的生成依赖于test.o文件(二进制目标文件);test.o文件的生成依赖于test.s文件(汇编语言文件);test.s文件的生成依赖于test.i文件(经过预处理后的C原始程序);test.i文件的生成依赖于test.c文件(源文件)。而单有依赖关系是无法通过依赖文件生成目标文件的,因为依赖关系只告诉了目标文件从何而来,并没有说如何根据依赖文件生成目标文件,因此还需要依赖方法,所谓依赖方法,其实就是我们平常在命令行上输入的一条条指令,如示例中:我们通过命令 gcc test.o -o test 可以从test.o文件得到test文件,这也就是依赖关系 test:test.o 对应的依赖方法;以此类推,gcc -c test.s -o test.o 是依赖关系 test.o:test.s 对应的依赖方法;gcc -S test.i -o test.s 是依赖关系 test.s:test.i 对应的依赖方法;gcc -E test.c -o test.i 是依赖关系 test.i:test.c 对应的依赖方法。有了依赖关系和依赖方法,我们就可以生成对应的目标文件。 有两点值得注意的是:每次编写依赖方法时应在依赖关系下另起一行并在开头空一个 Tab 键的位置,这是固定的编写格式;依赖文件列表可以为空,当其为空时,则表示不生成该目标文件,只执行相应的命令。
  • Makefile中使用 .PHONY 来声明伪目标,格式为:.PHONY:伪目标 。Makefile中的伪目标表示目标名称,并不代表真正的文件名,与实际存在的同名文件没有相互关系,因此伪目标不管同名文件是否存在都会执行对应的生成指令。伪目标的作用有两个:使目标对象无论如何都要重新生成;并不生成目标文件,而是为了执行一些指令。
  • Makefile中可以在行首使用 # 以表示行注释。

make的工作规则:

  • 在默认情况下,make会在当前目录下按顺序寻找文件名为 GUNmakefilemakefileMakefile 的文件。因此,如上图所示,执行make命令前需要先在项目所在当前目录下创建相应的文件(这里是Makefile)。
  • make的执行规则是:若make命令之后没有跟指定目标文件,则默认只生成Makefile中所有目标文件中的第一个(如图示例中的test文件即为第一个目标文件),否则就只生成指定目标文件。 但如果该目标文件的依赖文件不存在,make会根据语法规则递归生成第一个目标文件的所有依赖文件后再回头生成第一个目标文件,如上图示例中:一开始当前目录下只有源文件test.c,根据依赖关系,要生成第一个目标文件test,就先要生成其依赖文件test.o,以此类推,就是要依次先生成文件test.i、test.s、test.o,所以也可以看到,在执行make命令时,根据回显的命令执行过程,是按照与Makefile文件中编写的依赖方法的倒序来执行命令的,并且除第一个目标文件test外还生成了中间目标文件test.i、test.s、test.o。
  • 依赖方法(目标后所跟的命令)不总是被执行的,如果目标文件不存在,或是目标文件所依赖的文件的修改时间要比目标文件新,才会执行后面相应的命令以形成目标文件。也就是说,make会根据语法规则分析目标对象与依赖对象的时间信息,来判断是否在上一次目标生成后,源文件(依赖文件)发生了修改,若发生了修改,才需要重新生成。在最开始的图例中,一开始当前目录下是不存在任何目标文件的,所以执行make命令时,根据Makefile文件中的依赖关系,会正常生成所有的目标文件。而如果是目标文件已存在的情况,如下图所示:在当前目录下已存在目标文件,且目标文件的最近修改时间要比源文件的最近修改时间新,此时执行make命令表示当前目标已是最新,不再执行命令。而当通过 touch 命令更新源文件最近修改时间后 (说明: touch 命令在文件不存在时,会创建文件,而如果文件已经存在,则将文件的修改时间更新至最新;当然这里还可以通过重新编辑保存文件的方式来更新文件最近修改时间) ,使得源文件的最近修改时间新于当前目录下的目标文件,再次执行make命令则显示成功,并且当前目录下的目标文件的最近修改时间进行了更新,又新于源文件了。

时间问题示例

那如果想让某目标下的命令不论时间如何每次都能执行呢?如上Makefile编写规则中说到,可以采用声明伪目标的方式,使得对应命令总是被执行,但此处并不建议将目标可执行文件声明为伪目标,因为每次编译都需要一定的消耗,如果当前目标已是最新,没必要再执行命令生成一样的目标。

  • make在找寻依赖关系的过程中,如果出现错误,如:最后被依赖的文件找不到,那么make就会直接退出并报错;而对于所对应的命令(依赖方法)的错误,或是编译不成功,make仍会执行正确的命令再退出。具体示例如下:

错误示例

  • make只关注文件间的依赖关系,如果找到了依赖关系,但当前目录下却没有依赖关系中对应的依赖文件,那make将停止工作,如下图所示:移除当前目录下的test.c文件(最终依赖文件),虽然Makefile中编写了依赖关系,但由于找不到依赖文件,make停止了工作。

当前目录缺失依赖文件


关于项目清理:

工程是需要被清理的。如最开始图例中所示,我们使用了 .PHONY 声明了伪目标 clean (顾名思义,该目标的意义在于清理),这个伪目标并没有所谓的依赖文件,显然是起到上述中的第二个作用,即不生成目标文件,总是执行相应的指令。可以看到, clean 对应的指令为 rm -f test.i test.s test.o test ,目的在于清除所有的目标文件,以便重新编译,也就是所谓的项目清理。由于伪目标 clean 没有被第一个文件直接或间接关联,因此默认情况下执行 make 命令, clean 目标下的命令不会自动执行,需要通过指定目标,即以 make clean 的命令格式使得 clean 目标下的命令被执行。而基于伪目标的特性,无论当前目录下是否有同名文件clean,或是否有已经生成的目标文件,只要执行 make clean 命令,clean对应的指令都会被执行(如下图所示)。

项目清理


2 模拟实现进度条

在编写进度条程序之前,得先谈谈两个概念:\n\r

在我们编写C程序时常常会用到 \n 换行符来使内容另起一行输出,那 \r 又表示什么呢?其实在我们日常编辑中,可以看到,无论是输入还是输出,文字显示总是跟着光标的位置,光标在哪,则在哪输入输出。同样的,我们的程序在输出时也是跟随着光标的移动进行。\n 表示的是回车并换行,即在输出完当前内容后,将光标移动到下一行的行首再进行之后内容的输出,当然,光标的移动不是说突然就变到了那个位置,而是根据上下左右的方向一步步到达指定位置的,而光标移动到下一行行首的方式可以分为两种:一是先向左移动到当前行的行首,再向下移动到下一行;二是先向下移动到下一行,再向左移动到下一行行首。如图所示,键盘上的回车键等同于 \n ,其造型则表示出了光标的移动方式。而 \r 表示的是只回车不换行,即输出完当前内容后,将光标移动到当前的行的行首,因此,如果再碰到 \r 之后如果还有内容需要输出的话,会重新从当前行行首开始输出,覆盖上一次的输出内容。

回车键

\n 我们已经比较熟悉了,那下面以一个例子来看看 \r 对输出的影响:

  • 编写如下程序进行测试:

回车符测试代码

  • 测试结果:如图所示,当执行编译后生成的可执行程序test时,我们并没有看到相关内容的输出,执行后就输出了命令提示行。前面说到 \r 会使光标回到当前行行首,而后输出的内容会覆盖之前的内容,那是不是因为命令提示行输出覆盖太快所以没能看到内容显示呢?那我们试着在输出内容后进行延时。

回车符测试

  • 增加延时后测试结果:可以看到,增加延时后,依旧没有显示相关内容,只是在延时期间光标一直停在当前行首,直到延时结束输出命令提示行。

延时测试


回车符延时测试

  • 为什么使用了回车符 \r 后我们没能看到相关内容输出呢?这里不得不提到一个概念:缓冲区 。我们所编写的内容并不是直接输出到屏幕上(标准输出流)的,而是先输出到缓冲区中,在由缓冲区输出到屏幕。这里没能看到相关内容的输出是因为其仍保留在缓冲区中没有输出,只有当缓冲区刷新时,其中的保留的内容才会输出,而对于没有添加换行符和回车符或者添加的是换行符 \n 的情况,通常会自动刷新缓冲区,因此没有主动刷新也可以正常输出内容。那了解的原因所在后,我们在程序中主动刷新缓冲区,再看看输出结果:可以看到,增加刷新缓冲区后,相关内容正常显示了,也符合输出后光标回到当前行首,由命令提示行覆盖输出内容。

缓冲区刷新测试


缓冲区刷新测试


我们不是要编写进度条程序吗?这与 \r 有什么关系呢?想来进度条大家都不陌生,就是以一行上显示的移动进程来表示某项工作的进度。也就是说,从0-100的进度需要再同一行上进行变化,而 \r 具有输出内容后将光标移动到当前行首的作用,这就契合了进度条的变化过程,基于此,以下模拟实现进度条:

  • 我们将进度条程序的主要实现封装为一个函数,编写在 proc.c 文件中,将对应的函数声明编写在头文件 proc.h 中,再创建测试文件 procTest.c ,在其中编写主函数并调用进度条函数。这里实现了两种不同形式的进度条:符号移动版和色块移动版。

    proc.h
    proc.h
    proc.c(后附源代码,有需要者可再自行演示)
    proc.c
    procTest.c
    procTest.c


    进度条函数源代码:
#include "proc.h"

#define STYLE '=' //进度条移动符号
    
//符号形式移动版    
void process(){    
  char bar[101];//进度条字符串,预留一个'\0'的位置    
  char status[4] = {'|', '/', '-', '\\'}; //表示运行状态,循环数组中的字符    
  memset(bar, '\0', sizeof(bar));//初始将进度条字符串中的内容全部置为'\0'    
  int i = 0;//考虑执行标准问题,这里在外初始化    
  for(; i <= 100; i++){    
    //控制格式,循环输出进度条字符串,通过字符串的变化来表示进度条的变化    
    //\033[选项;选项;选项m表示对其后输出内容的颜色控制,0m默认无颜色    
    printf("[\033[0;36m%-100s\033[0m][%d%%][%c]\r", bar, i, status[i%4]);    
    fflush(stdout);//刷新缓冲区,确保字符串内容正常输出    
    bar[i] = STYLE;//每次输出后修改一个字符为对应的进度条移动符号    
    if(i < 99)    
    bar[i+1] = '>';//增加箭头显示,当进度达到100%,去掉箭头    
    usleep(100000);//延时0.1s显示    
  }    
  printf("\n");//结束后换行输出命令提示行    
}    
    
//色块形式移动版                                                                                                                                           
void process_color(){    
  char bar[102];//进度条字符串,预留两个'\0'的位置    
  char status[4] = {'|', '/', '-', '\\'};     
  memset(bar, ' ', sizeof(bar));//初始将字符串中内容全部置' '(空字符)    
  bar[101] = '\0';//保持最后一个字符总是为'\0'    
  int i = 0;                                                                                                                                               
  //控制中间的'\0'字符将整个字符数组分为两个字符串输出,确保两个字符串的长度加起来总是为100
  //循环输出两个字符串,控制前一个字符串总是有颜色,后一个字符串总是无颜色
  for(; i <= 100; i++){
    bar[i] = '\0';//控制分隔字符串的'\0'移动
    //输出带背景色的空字符串与不带背景色的空字符串
    printf("[\033[0;30;46m%s\033[0m%s][%d%%][%c]\r", bar, bar+i+1, i, status[i%4]);
    fflush(stdout);//刷新缓冲区
    bar[i] = ' ';//将前一个字符串中的内容均置为空字符' '
    usleep(100000);//延时0.1s显示
  }
  printf("\n");//结束后换行输出命令提示行
}

  • 前面我们讲解了make与Makefile的基本使用,接下来我们就可以在进度条程序中使用起来了,如下,先在当前目录中创建Makefile文件,文件编写如下:
    Makefile
    接着执行make命令生成进度条可执行程序如下:
    生成进度条可执行程序

  • 结果演示(网络原因可能稍有卡顿):
    符号移动版:
    符号版进度条
    色块移动版:
    色块版进度条

以上是我对make与Makefile工具使用的一些学习记录总结,如有错误,希望大家帮忙指正,也欢迎大家给予建议和讨论,谢谢!

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

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

相关文章

使用 EMQX Cloud 桥接数据到 GCP Pub/Sub

前不久&#xff0c;Google 宣布其旗下的 GCP IoT Core 即将在 2023 年 8 月 16 日停止提供服务。这意味着大量使用 GCP IoT Core 的用户可能需要将他们的 IoT 应用迁移到其他物联网云服务。除了云服务的迁移&#xff0c;很多用户也在直接利用谷歌云生态&#xff0c;使用 GCP 上…

Docker部署 Harbor

系列文章目录 Docker部署 registry Docker搭建 svn Docker部署 Harbor Docker 部署SQL Server 2017 Docker 安装 MS SqlServer Docker部署 Oracle12c Docker部署Jenkins Docker部署 Harbor系列文章目录前言一、Harbor安装有3种方式二、安装步骤1. 从github官方地址下载安装包2…

C/C++尖括号和双引号包含头文件的区别

前言头文件有两种包含方式&#xff0c;一种是使用尖括号<>&#xff0c;另外一种是通过双引号""包含&#xff0c;例如&#xff1a;#include <iostream> #include "add.h"那么今天就专门来聊一聊这两种方式的区别。1.头文件的含义不同使用尖括号…

多表连接查询

语法&#xff1a; select ... from a join b on a和b的连接条件 join c on a和c的连接条件 join d on a和d的连接条件 一条SQL语句中内连接与外连接可以混合使用 案例&#xff1a;查询每个员工的部门名称以及薪资等级&#xff0c;要求显示员工名、部门名、薪资和薪资等级 SQL&…

核心乐理---和弦基础

和弦命名 什么是和弦 三个或三个以上的三度堆叠成为和弦 三和弦 三和弦指的是有三个音的和弦 七和弦是指七度的和弦&#xff0c;共4个音 其余的和弦与七和弦命名规则相同&#xff0c;跨越几度就是几和弦 九和弦是指跨越九度的和弦&#xff0c;共5个音十一和弦是指跨越十一…

【阶段三】Python机器学习18篇:机器学习项目实战:AdaBoost算法的核心思想、原理与数学原理举例

本篇的思维导图: AdaBoost算法的核心思想 AdaBoost算法(Adaptive Boosting)是一种有效而实用的Boosting算法,它以一种高度自适应的方式按顺序训练弱学习器。针对分类问题,AdaBoost算法根据前一次的分类效果调整数据的权重,在上一个弱学习器中分类错误的样本的权…

和Nginx相关的TCP/IP中反向代理系统解析与调优

文章目录前言反向代理系统分析消耗资源分析CPUMemory网卡压测与调优实战压测数据对比什么是TIME_WAITkeepalive开启前后数据对比小结关于后续端口不足&#xff0c;限制连接扩展协议层面无法充分做到连接的复用TCP优化分享相关文章&#xff1a;前言 本文介绍aeproxy这个应用以及…

IB地理科学什么?

IB地理科是一门很特别的科目&#xff0c;能帮助同学掌握技巧认识和了解这世界&#xff0c;而这课程分为两部分&#xff0c;包括自然环境和人文社会。IB地理科两部分 首先是自然环境&#xff0c;包括生态系统、气候&#xff0c;地壳活动等等 &#xff1b;另外是人文社会&#x…

Nosql和Redis介绍,Redis五大数据类型及操作,跳跃表

NoSQL数据库是用来解决性能问题的&#xff0c;分很多类。redis是NoSQL的一种。 NoSQL的引入&#xff1a; 随着Web2.0时代的到来。可以进行网络请求的不仅限与电脑。用户还可以通过手机端&#xff0c;平板甚至汽车等来进行网络请求。网络请求极具增加&#xff0c;增加了服务器…

【代码随想录】LC 102. 二叉树的层序遍历

目录 一、题目 1、原题链接 2、题目描述 二、解题报告 1、思路分析 2、时间复杂度 3、代码详解 三、知识风暴 一、题目 1、原题链接 力扣 2、题目描述 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问…

linux下miniconda环境的配置以及软件的安装

miniconda 我们需要在自己的目录下安装conda环境&#xff0c;所以需要自定义安装位置 mkdir /share/nas6/wangyq/biosoft/miniconda wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /share/nas6/wangyq/biosoft/miniconda/miniconda.sh注意…

070-JAVA项目实训:仿QQ即时通讯软件系列讲座五(讲解用户注册功能)

【上一讲】069-JAVA项目实训:仿QQ即时通讯软件讲座四(讲解系统登录功能)_CSDN专家-赖老师(软件之家)的博客-CSDN博客 本文主要内容是实现注册QQ用户功能,自动获取本机IP地址,与系统用户判断端口是否唯一,使用的主要技术如下: 1.使用数据库技术完成注册功能; 2.自动…

Linux系统编程——锁

目录线程同步:锁的使用:使用matex(互斥量、互斥锁)一般步骤:restrict关键字:死锁:读写锁&#xff1a;条件变量实现生产者-消费者代码信号量实现消费者生产者线程同步: 协同步调&#xff0c;对公共区域数据按序访问。防止数据混乱&#xff0c;产生与时间有关的错误。 锁的使用…

原来这些技术标准,是字节跳动人参与制定的

我们常常遇到这类问题&#xff1a;因为不同厂商产品标准不同、接口不一致&#xff0c;导致 A 产品无法兼容 B 产品&#xff0c;C 接口和 D 接口无法连接&#xff0c;实在令人困扰。其实&#xff0c;技术从业者们一直在试图解决这些不统一、不兼容的问题&#xff0c;他们的方法是…

ceres学习笔记(一)

本来还想着先对照着官方doc来学习的。突然在csdn里面搜了下&#xff0c;发现了几篇高质量的文章&#xff0c;就先对应这几篇文章学习&#xff0c;来快速入门。 一、ceres求解问题一般步骤 使用ceres-solver求解优化问题一般分为下面三步&#xff1a; 1.第一步&#xff1a;构…

aws cdk 配置 lambda 函数的金丝雀发布

之前的文章介绍了使用sam框架完成lambda函数的金丝雀发布&#xff0c;这里使用cdk创建lambda函数项目实现此功能 Building CI/CD pipelines for lambda canary deployments using AWS CDK 项目的结构如下图所示 lambda堆栈示例 应用程序和环境配置 #!/usr/bin/env python3 im…

数据结构与算法0—大纲

数据结构&#xff1a;数据结构是计算机存储、组织数据的方式。是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素之间的关系组. 算法&#xff1a;是指解题方案的准确而完整的描述&#xff0c;是一系列解决问题的清晰指令&#xff0c;算法代表着用系统的方法…

Vue动态路由、动态路由如何进行参数的传递、$router和$route的区别、命名路由、命名视图

一、什么是动态路由 ​ 将URL地址中可变的内容设置成参数&#xff0c;根据不同的参数渲染不同的组件。&#xff08;组件可以复用&#xff09; 二、动态路由如何进行参数的传递 ​ 1. 如何设置URL地址中的参数&#xff1a;“/url/:参数名” ​ 2. 在组…

Linux环境下配置Nginx

文章目录安装环境配置安装Nginx需要安装第三方的开发包安装Nginx启动并访问Nginx安装完毕Linux——centos7版本 安装环境配置 Nginx是C语言编写的&#xff0c;所以需要配置C语言编译环境&#xff08;要联网&#xff09; 安装gcc环境 [roota ~]# yum install gcc-c 已加载插件…

【Unity3D编辑器扩展】Unity3D中解决Text的清晰度问题

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址我的个人博客 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 在程序开发中&#xff0c;常常会遇到Unity中Text文字不清晰的问…